6

我想我没有正确理解工作流程。我正在使用 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 主题,但没有找到任何有用的信息。

4

2 回答 2

7

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

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

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

例如,在 Web 应用程序中,每个ServletRequest 都会自动ShiroFilter执行此设置/绑定/取消绑定逻辑。 我怀疑基于 Akka 的应用程序中的某些东西(一些“框架”代码或组件)也会执行相同的设置/绑定/取消绑定逻辑。

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

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

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

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

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

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

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

于 2013-07-25T16:47:31.517 回答
5

跟进这个问题,当我遇到同样的问题时,这非常有用。主要目标是提供一些关于 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。

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

于 2016-02-01T23:14:39.467 回答