我想我不能正确理解工作流程.我正在使用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主题,但没有找到任何有用的信息.
这绝对是可能的,但您必须确保SubjectAkka组件(演员)在处理消息时可以使用它.
我熟悉Akka的架构(演员/消息传递模型),但我自己没有使用Akka,所以请将以下内容作为最佳猜测答案:
在传统的基于Shiro的应用程序和/或web-apps中,某些东西负责构建Subject反映当前调用者和/或请求的实例,然后将其绑定到当前正在执行的实例Thread.这可确保在Thread执行期间的任何后续调用都能SecurityUtils.getSubject()正常运行.这些都记录在Shiro的Subject文档中(参见Subject.Builder和Thread 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)中如何工作.希望这能为您提供足够的信息,帮助您以与您的用例相关的方式解决此问题!
跟进这个问题,当我遇到同样的问题时,这非常有用。主要目标是提供有关 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。
希望这是一个有用的资源,因为几乎不可能找到这两个框架之间集成的示例。