如何将JAAS授权检查委派给Shiro?

64

我正在开发一个需要基于对象进行身份验证和授权的服务器端应用程序。我喜欢Shiro框架的简单性,但为了与JAAS兼容,我编写了一个使用Apache Shiro作为底层机制的LoginModule。

但我的问题是,我无法找到一种将JAAS授权检查委托给Shiro的方法。我该如何实现这一点?


2
Deniz,你找到使用Shiro和JAAS一起的方法了吗?如果没有,那你采取了什么方法?谢谢,Kevin - Kevin Welker
5
我的主要关注点是使用Shiro来实现JMX安全,其中主要安全方法使用JAAS。我通过实现一个JMXAuthenticator解决了这个问题,它在当前访问控制上下文中创建了一个可变的JAAS subject,并将Shiro subject存储在JAAS subject的私有凭据集中。后来,我实现了一个LoginModule,实际上是Shiro Authenticator接口的包装器(由SecurityManagers扩展)。 - Deniz Acay
也许您可以提供更多关于问题的信息,包括您的LoginModule代码、错误信息以及运行时配置。 - Martín Straus
1个回答

4
注意:本答案涉及将外部授权系统通过标准安全框架集成到JVM中的一般情况。它不是Shiro或JMX特定的,因为我对两者都不熟悉。

从概念上看,你似乎需要的是策略决策点(PDP)——即评估授权查询("实体X是否被允许执行Y?")的设施。JDK提供了几个这样的设施:

  1. 有效的SecurityManager,特别是其checkXXX方法组。
  2. ProtectionDomain类,特别是其implies(Permission)方法。
  3. 有效的Policy的关键implies(ProtectionDomain, Permission)方法。
  4. 次要的是CodeSourcePermissionCollectionPermissionPrincipalimplies方法。
任何上述方法均可被覆盖,以便在升序粒度上自定义概念PDP的功能。需要注意的是,JAAS并没有(与其名称所暗示的相反)真正带来自己的PDP;相反,它提供了域和策略支持基于主体查询的手段,除了代码来源的原始信任因素之外。因此,在我看来,您要求保持“JAAS兼容”基本上意味着想要使用(原始加JAAS)Java SE授权模型,即沙箱,我怀疑这不是您想要的。当标准模型被认为过于低级和/或性能密集时,通常会使用Shiro等框架;换句话说,当授权逻辑不需要为给定一组信任因素评估每个单个堆栈帧时,由于这些因素更频繁地与上下文无关而不是有关。根据我的假设的有效性,出现了三种主要情况需要审查:
  1. 授权与AccessControlContext无关。Shiro本地的授权属性(SNAAs)无论是什么,都适用于整个线程。代码来源并不重要。
  2. 代码来源很重要,必须使用沙箱。SNAAs仍然与AccessControlContext无关。
  3. 代码来源和SNAAs都很重要,并且与AccessControlContext有关。

1. 仅基于SNAAs的授权

  1. 管理身份验证的方式由您自行决定。如果您希望继续使用JAAS的javax.security.auth SPI进行身份验证,则无需建立标准的Subject作为身份验证结果,而是直接将Shiro特定的结果与线程本地存储相关联。这样,您可以更方便地访问SNAAs,并避免使用AccessControlContext(并避免可能出现的性能损失)来检索它们。

  2. 子类化SecurityManager,覆盖至少两个checkPermission方法,使其:

    1. 在委托给SPDP之前,将Permission参数(如有必要)转换为Shiro的PDP(SPDP)可以理解的内容。
    2. 使用线程本地SNAAs和权限进行委托,并在SPDP发出访问拒绝信号时抛出SecurityException

    接收安全上下文的重载版本可以简单地忽略相应的参数。在应用程序初始化时,实例化并安装(System::setSecurityManager)您的实现。


2. 混合授权,将代码来源与上下文无关的SNAAs结合起来

  1. 按照您认为合适的方式管理身份验证;再次将Shiro特定的Subject与线程本身关联。
  2. 子类化SecurityManager,重写至少两个checkPermission方法,这次它们应该委托给SPDP和/或被覆盖的实现(后者依次调用当前或提供的访问控制上下文中的checkPermission)。当要查询哪些权限以及以什么顺序查询时,当然取决于实现。当两者都要被调用时,应首先查询SPDP,因为它可能会比访问控制上下文更快地响应。
  3. 如果SPDP还要处理源自某个位置和/或代码签名者集的代码授予权限的评估,则还必须子类化Policy,实现implies(ProtectionDomain, Permission),使其像上面的SecurityManager::checkPermission一样,传递一些可理解的域(通常只有它的CodeSource)和权限参数,但逻辑上不包括SNAAs。该实现应尽可能高效,因为它将在每个访问控制上下文的每个域上一次调用checkPermission时间。实例化并安装(Policy::setPolicy)您的实现。

3. 混合授权,结合代码来源和SNAAs,均为上下文敏感。
  1. 根据需要管理身份验证。不幸的是,在这种情况下,处理主题的部分并不像创建ThreadLocal那么简单。

  2. 子类化、实例化和安装执行SecurityManager::checkPermissionPolicy::implies组合职责的Policy,如第二个案例中所描述的。

  3. 实例化并安装标准的SecurityManager

  4. 创建一个ProtectionDomain子类,能够存储和公开SNAAs。

  5. 编写DomainCombiner

    1. 使用SNAAs构造;

    2. 实现combine(ProtectionDomain[], ProtectionDomain[]),使其

      1. 用自定义实现的等效实例替换第一个(“当前”上下文)数组参数的域;
      2. 然后将第二个(“分配”或“继承”上下文)参数的域按原样附加到前者后面;最后
      3. 返回连接结果。

    Policy::implies一样,实现应该高效(例如通过消除重复项),因为每次调用getContextcheckPermissionAccessController方法时都会调用它。

  6. 在成功身份验证后,创建一个新的AccessControlContext,其中包装了当前上下文以及自定义DomainCombiner的实例,依次包装SNAAs。将要执行的代码包装在AccessController::doPrivilegedWithCombiner调用中,并传递替换的访问控制上下文。


1 相较于使用自定义域和自己的组合器实现,还有一种看似更简单的选择,即将 SNAAs 转换为 Principal 并使用标准的 SubjectDomainCombiner 将它们绑定到当前的 AccessControlContext 的域中(如上所述,或仅通过 Subject::doAs)。这种方法是否降低了策略的效率主要取决于调用堆栈的深度(访问控制上下文包含多少个不同的域)。最终,您认为可以避免作为域组合器一部分实现的缓存优化将在编写策略时回击您,因此这基本上是您必须在那时做出的设计决策。


1
虽然我在6年前找到了解决方案并回答了自己的问题,但你在这里提供的有用信息对于所有遇到这个问题的人来说绝对值得成为一个被接受的答案。 - Deniz Acay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接