如何让 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.Builder 和 Thread 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 环境中工作的主要内容,如果未能解决你的问题,请参考以下文章
Akka演员(Scala)如何在内存不足时获得堆转储[重复]
如何使用 scala 2.9.x 运行 akka 2.1-snapshots?