如何让Shiro在Scala + Akka + Spray环境中工作

4le*_*x1v 6 shiro akka spray

我想我不能正确理解工作流程.我正在使用Apache Shiro和Stormpath在Scala中编写Web服务.我的用户身份验证过程如下所示:

1)从POST请求中获取用户数据,使用Stormpath进行检查,如果一切正常,请重定向到某个页面:

pathPrefix("api") {
  path("login") {
    post {
      AuthToken.fromRequest { (token: AuthToken) =>
        stormpathAuth(token) { subj =>
          log.info("Subj {}", subj.getPrincipal.toString)
          redirect("/some/page", StatusCodes.Found)
        }
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

在日志中没关系,Shiro用Stormpath帐户给我一个正确的主题.接下来我想提取主题,在代码中使用它:

pathPrefix("some") {
  loggedInUser { subject =>
    path("page") {
      get {
        complete {
          html.render(Page.model)
        }
      }
    } ..... other routes
Run Code Online (Sandbox Code Playgroud)


loggedInUser指令应提取主题并检查它是否经过身份验证,否则重定向到登录表单.问题是它总是将我重定向到登录表单,尽管在日志中SubjectUtils.getSubject.getPrincipal显示正确的帐户.

更新

实际上Spray是建立在Akka之上的.所以我认为问题落后于getSubject实现,目前依赖于ThreadLocal环境.我搜索了Shiro + Akka主题,但没有找到任何有用的信息.

Les*_*ood 6

这绝对是可能的,但您必须确保SubjectAkka组件(演员)在处理消息时可以使用它.

我熟悉Akka的架构(演员/消息传递模型),但我自己没有使用Akka,所以请将以下内容作为最佳猜测答案:

在传统的基于Shiro的应用程序和/或web-apps中,某些东西负责构建Subject反映当前调用者和/或请求的实例,然后将其绑定到当前正在执行的实例Thread.这可确保在Thread执行期间的任何后续调用都能SecurityUtils.getSubject()正常运行.这些都记录在Shiro的Subject文档中(参见Subject.BuilderThread Association部分).

例如,在web-app中,每个 ServletRequest 自动ShiroFilter执行此setup/bind/unbind逻辑 .我怀疑基于Akka的应用程序中的某些东西(某些"框架式"代码或组件)也会执行相同的setup/bind/unbind逻辑.

现在有了Akka,我相当肯定你可以使用上面文档中介绍的传统基于线程的方法(我认为Play!用户已经成功完成了这项工作).但是Akka不可变消息可能有另一个有趣的方法:

  • 构造消息时,您可以将特定于主题的信息附加到消息(例如消息"标题"),其中包含Shiro PrincipalCollection和身份验证状态(是否经过身份验证)以及其他任何内容(runAs状态,无论如何).

  • 然后,当接收到消息时,该信息将用作Subject.Builder创建Subject实例的输入,并且在消息传递处理期间使用该Subject实例.Shiro Subject实例非常轻量级,预计会根据请求创建和销毁(如果需要,甚至可以在每个请求中多次),因此您无需担心Builder开销.

  • Subject建成后,你可以绑定,然后将其解除绑定到当前执行的线程,或者,每个处理邮件可以通过这个相同的逻辑的方式"frameworky"排序演员.后一种方法根本不需要线程绑定,因为主题状态现在维持在消息级别,而不是线程级别.

作为这种替代(非线程)方法的证明,即将推出的ActiveMQ Shiro插件使用连接状态来存储Shiro状态,而不是线程.同样,消息状态也可以轻松使用.

请注意,使用非基于线程的方法,下游调用方无法调用SecurityUtils.getSubject()以获取Subject实例.他们必须以另一种"框架"方式获得它.

再一次,这是我最好的分析,如果没有自己使用Akka,它在消息传递环境(如Akka)中如何工作.希望这能为您提供足够的信息,帮助您以与您的用例相关的方式解决此问题!


VeX*_*tos 5

跟进这个问题,当我遇到同样的问题时,这非常有用。主要目标是提供有关 Les Hazlewood 建议实施的一些额外信息。另一个很好的替代信息来源也是这个话题https://groups.google.com/forum/#!topic/spray-user/wpiG4SREpl0

Shiro 当前是基于线程的,这意味着它将主题信息绑定到当前线程。这对 Akka 来说是一个问题,因为它使用调度程序和工作线程的线程池。

随着线程被重用,主题从一个请求泄漏到另一个请求中,导致未经身份验证的请求被处理为经过身份验证以及相反的情况。

正如 Les 建议的那样,该问题的一个可能解决方案是放弃线程绑定并将主题存储在 Akka 消息中。为此,这意味着无法使用 Shiro 的 SecurityUtils 上提供的静态方法。操作应该直接在 Subject 上完成。此外,该主题应使用 Subject.Builder 构建。

为此,您可以使用主题包装您的消息,

case class ActorMessage(subject:Subject, value: Any)


object MessageSender {

def ? (actorRef: ActorRef, message: Any)(implicit subject: Subject): Future[Any] = {
  val resultFuture = if (!message.isInstanceOf[ActorMessage]) {
      val actorMessage = ActorMessage(subject, message)
      actorRef ask actorMessage
  } else actorRef ask message

  for (result <- resultFuture) yield {
    if (result.isInstanceOf[ActorMessage]) {
      val actorMessageResp = result.asInstanceOf[ActorMessage]
      actorMessageResp.value
    } else result
  }
}

}
Run Code Online (Sandbox Code Playgroud)

并在 actor 收到消息时将它们解包到 actor 上。或者,如果它是请求条目参与者,则初始化主题。

abstract class ShiroActor extends Actor {

implicit var shiroSubject: Subject = (new Subject.Builder).buildSubject

override def aroundReceive(receive: Actor.Receive, msg: Any): Unit = {
    if (msg.isInstanceOf[ActorMessage]) {
      val actorMessage = msg.asInstanceOf[ActorMessage]
      shiroSubject = actorMessage.subject
      receive.applyOrElse(actorMessage.value, unhandled)
    } else {
     shiroSubject = (new Subject.Builder).buildSubject
      receive.applyOrElse(msg, unhandled)
    }
  }

}
Run Code Online (Sandbox Code Playgroud)

现在要使用它,实现的actor 必须扩展ShiroActor 并在actor 之间交换消息,您必须使用MessageSender,而不是ActorRef ask 或tell 方法。

要登录主题、检查权限、角色等,您现在可以使用演员上可用的主题。像这样,

shiroSubject.login(new AuthenticationToken(principal, credentials))
Run Code Online (Sandbox Code Playgroud)

这假设登录是在请求条目参与者上完成的,并且主题只是共享以检查后续参与者的权限。但我相信它可以很容易地适应双向更新演员的 shiroSubject。

希望这是一个有用的资源,因为几乎不可能找到这两个框架之间集成的示例。