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

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

但我的问题是,我找不到将JAAS授权检查委托给Shiro的方法。我怎样才能做到这一点?


答案 1

注意:答案解决了外部授权系统通过标准安全框架与JVM集成的一般情况。它不是特定于Shiro或JMX的,因为我对两者都不熟悉。


从概念上讲,您似乎在策略决策点 (PDP) 之后 - 即评估授权查询(“实体 X 是否允许执行 Y?”)的工具。JDK 提供了以下几个:

  1. 有效的安全管理器,特别是其方法组。checkXXX
  2. 保护域类,尤其是其方法。implies(Permission)
  3. 有效政策的关键方法。implies(ProtectionDomain, Permission)
  4. 其次,代码源权限集合权限主体的方法implies

可以重写上述任何方法,以便以递增的粒度自定义概念PDP的功能。应该指出的是,JAAS确实(与其名称所暗示的相反)并没有真正带来自己的PDP;相反,它为域和策略提供了支持基于主体的查询的方法,以及代码来源的原始信任因素。因此,在我看来,您保持“JAAS兼容”的要求基本上意味着想要使用(原始加JAAS)Java SE授权模型,即沙箱,我怀疑这是否是您想要的。当标准模型被认为水平太低和/或性能密集型时,倾向于使用像Shiro这样的框架;换句话说,当授权逻辑不需要评估给定的一组信任因素的每个堆栈帧时,因为这些因素通常对上下文不敏感。根据我假设的有效性,出现了三种主要情况进行检查:

  1. 授权是独立于的。Shiro-native 授权属性 (SNAAs),无论它们是什么,都适用于整个线程。代码来源无关紧要。AccessControlContext
  2. 代码来源很重要,强制要求使用沙盒。短期NAAs仍然是独立的。AccessControlContext
  3. 代码来源和 SNAA 既相关又相互依赖AccessControlContext

1. 完全基于标准种姓的授权

  1. 以您认为合适的方式管理身份验证。如果您希望继续使用 JAAS 的 SPI 进行身份验证,请忘记建立标准作为身份验证结果,而是直接将 Shiro 特定的标准绑定到线程本地存储。通过这种方式,您可以更方便地访问 SNAA,并避免使用(并遭受潜在的性能损失)来检索它们。javax.security.authSubjectAccessControlContext

  2. 子类 ,重写至少两个方法,使得它们SecurityManagercheckPermission

    1. 如有必要,将论点翻译成Shiro的PDP(SPDP)理解的东西,然后再Permission
    2. 使用线程本地 SNAA 和权限委派给 SPDP(并抛出应 SPDP 信号访问拒绝)。SecurityException

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


2. 混合授权,将代码来源与上下文不敏感的SNAAs相结合

  1. 以您认为合适的方式管理身份验证;再次将Shiro特定与线程本身相关联。Subject
  2. 子类 ,重写至少两个方法,这次它们同时委托给 SPDP 和/或被覆盖的实现(这反过来又相应地调用当前或提供的访问控制上下文)。对于任何给定的许可,应该参考哪一个(或几个)以及以什么顺序进行咨询当然取决于实现。当两者都被调用时,应首先查询 SPDP,因为它的响应速度可能比访问控制上下文快。SecurityManagercheckPermissioncheckPermission
  3. 如果 SPDP 要另外处理授予来自某个位置和/或一组代码签名者的代码的权限的评估,则还必须进行子类化 ,实现实现,如上所述,它将域的一些可理解的表示形式(通常只有其)和权限参数(但逻辑上不是 SNAAs)传递给 SPDP。实现应该尽可能有效,因为它将一次在每个域的每个访问控制上下文被调用一次。实例化并安装 () 您的实现。Policyimplies(ProtectionDomain, Permission)SecurityManager::checkPermissionCodeSourcecheckPermissionPolicy::setPolicy

3. 混合授权,将代码来源与 SNAA 相结合,两者都与上下文相关

  1. 以您认为合适的方式管理身份验证。不幸的是,在这种情况下,主题处理部分并不像创建一个那么简单。ThreadLocal

  2. 子类、实例化和安装 执行 和 的组合职责,如第二种情况中单独描述的那样。PolicySecurityManager::checkPermissionPolicy::implies

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

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

  5. 作者1 一个域名复合者

    1. 是用SNAAs构建的;

    2. 实现这样的combine(ProtectionDomain[], ProtectionDomain[])

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

    就像 ,实现应该是有效的(例如,通过消除重复项),因为每次 和 方法都会调用它。Policy::impliesgetContextcheckPermissionAccessController

  6. 身份验证成功后,创建一个包装当前身份验证的新身份验证,以及自定义的实例,从而包装 SNAA。包装要在调用“内”在该点之外执行的代码,同时传递替换访问控制上下文。AccessControlContextDomainCombinerAccessController::doPrivilegedWithCombiner


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


答案 2

推荐