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

Posted

技术标签:

【中文标题】如何让 Shiro 在 Scala + Akka + Spray 环境中工作【英文标题】:How can i make Shiro work in Scala + Akka + Spray environment 【发布时间】:2013-07-22 09:25:57 【问题描述】:

我想我没有正确理解工作流程。我正在使用 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)
        
      
    
  

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

pathPrefix("some") 
  loggedInUser  subject =>
    path("page") 
      get 
        complete 
          html.render(Page.model)
        
      
     ..... other routes

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

更新

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

【问题讨论】:

你在用Shiro Stormpath plugin吗? 另外,这是在 Servlet 容器中运行还是在 Play 中运行! ? @LesHazlewood 是的,我正在使用 Stormpath 插件进行用户身份验证,不,我有自己的服务器和客户端,使用 Spray toolkit、spray-can 和 spray-routing 构建 @LesHazlewood 我已经更新了这个问题。我在内部使用 Akka。有什么方法可以让 Shiro 在 Akka 环境下工作? 【参考方案1】:

这绝对是可能的,但您必须确保 Subject 在 Akka 组件(参与者)处理消息时可用。

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

在传统的基于 Shiro 的应用程序和/或 Web 应用程序中,something 负责构建一个反映当前调用者和/或请求的 Subject 实例,然后将其绑定到当前正在执行的Thread。这确保了在该线程执行期间对SecurityUtils.getSubject() 的任何后续调用都能正确运行。这一切都记录在 Shiro's Subject documentation 中(参见 Subject.BuilderThread Association 部分)。

例如,在网络应用程序中,ShiroFilter 执行 this setup/bind/unbind logic automatically per ServletRequest。我怀疑基于 Akka 的应用程序中的某些东西(一些“框架”代码或组件)也会执行相同的设置/绑定/取消绑定逻辑。

现在有了 Akka,我相当肯定您可以使用上述文档中介绍的传统的基于线程的方法(我认为 Play!用户已经成功地做到了这一点)。但是另一种有趣的方法可能适用于 Akka 不可变消息:

构造消息时,您可以将特定于主题的信息附加到消息(例如消息“标头”)中,例如 Shiro PrincipalCollection 和身份验证状态(是否已通过身份验证)以及其他任何内容(runAs状态,随便)。

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

Subject 被构建时,你可以绑定然后解除绑定到当前正在执行的线程,或者,每个处理消息的 Actor 可以以“框架”的方式通过相同的逻辑.后一种方法根本不需要线程绑定,因为现在主题状态是在消息级别而不是线程级别维护的。

作为对这种替代(非基于线程)方法的证明,即将推出的 Shiro plugin for ActiveMQ 使用连接状态来存储 Shiro 状态,而不是线程。同样,消息状态也可以很容易地使用。

请注意,使用非基于线程的方法,下游调用者无法调用SecurityUtils.getSubject() 来获取Subject 实例。他们将不得不以另一种“框架”方式获得它。

再次,这是我在自己未使用 Akka 的情况下如何在消息传递环境(如 Akka)中工作的最大努力分析。希望这可以为您提供足够的信息,帮助您以与您的用例相关的方式解决此问题!

【讨论】:

【参考方案2】:

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

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

随着线程被重用,主体从一个请求泄漏到另一个请求,导致未经身份验证的请求被处理为经过身份验证的,反之亦然。

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



并在收到消息时将它们解包到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)
    
  


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

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

shiroSubject.login(new AuthenticationToken(principal, credentials))

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

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

【讨论】:

以上是关于如何让 Shiro 在 Scala + Akka + Spray 环境中工作的主要内容,如果未能解决你的问题,请参考以下文章

Scala框架Akka学习

Akka演员(Scala)如何在内存不足时获得堆转储[重复]

如何使用 scala 2.9.x 运行 akka 2.1-snapshots?

Scala如何使用akka actor有效地处理超时操作

如何在scala akka(spray)中为rest服务编写测试用例

如何在 Scala Akka 中停止 system.scheduler.schedule