我正在开发一个需要基于对象进行身份验证和授权的服务器端应用程序。我喜欢 Shiro 的简单,但是为了兼容 JAAS,我写了一个 LoginModule,它使用 Apache Shiro 作为底层机制。
但我的问题是我找不到将 JAAS 授权检查委托给 Shiro 的方法。我怎样才能做到这一点?
我正在开发一个需要基于对象进行身份验证和授权的服务器端应用程序。我喜欢 Shiro 的简单,但是为了兼容 JAAS,我写了一个 LoginModule,它使用 Apache Shiro 作为底层机制。
但我的问题是我找不到将 JAAS 授权检查委托给 Shiro 的方法。我怎样才能做到这一点?
注意:答案解决了通过标准安全框架将外部授权系统与 JVM 集成的一般情况。它不是 Shiro 或 JMX 特定的,因为我都不熟悉。
从概念上讲,您似乎在策略决策点 (PDP) 之后——即评估授权查询(“实体 X 是否允许执行 Y?”)的设施。JDK 提供了其中的几个:
SecurityManager
,特别是它的checkXXX
一组方法。ProtectionDomain
类,尤其是它的方法implies(Permission)
。implies(ProtectionDomain, Permission)
方法Policy
。implies
方法。CodeSource
PermissionCollection
Permission
Principal
可以覆盖任何上述方法,以便以递增的粒度自定义概念 PDP 的功能。应该指出的是,JAAS 确实(与其名称所暗示的相反)并没有真正带来自己的 PDP。相反,除了代码来源的原始信任因素之外,它还为域和策略提供了支持基于主体的查询的方法。因此,在我看来,您对保持“JAAS 兼容”的要求基本上转化为想要使用(原始加 JAAS)Java SE 授权模型,也就是沙箱,我怀疑它是您想要的。当标准模型被认为太低级和/或性能密集时,往往会使用 Shiro 等框架;换句话说,当授权逻辑不需要为给定的一组信任因素评估每个堆栈帧,因为这些因素更频繁地是上下文不敏感的。根据我的假设的有效性,需要检查三个主要案例:
AccessControlContext
独立的。Shiro-native 授权属性 (SNAA),无论它们是什么,都适用于整个线程。代码来源无关紧要。AccessControlContext
。AccessControlContext
依赖的。以您认为合适的方式管理身份验证。如果您希望继续使用 JAAS 的javax.security.auth
SPI 进行身份验证,请忘记建立一个标准Subject
作为身份验证结果,而是直接将 Shiro 特定的标准绑定到线程本地存储。通过这种方式,您可以更方便地访问 SNAA,并避免使用AccessControlContext
(并遭受潜在的性能损失)来检索它们。
子类SecurityManager
,至少覆盖两个checkPermission
方法,使得它们
Permission
论证翻译成 Shiro 的 PDP (SPDP) 可以理解的内容,之前SecurityException
在 SPDP 信号访问拒绝时抛出一个)。安全上下文接收重载可以简单地忽略相应的参数。在应用程序初始化时,实例化并安装(System::setSecurityManager
)您的实现。
Subject
与线程本身相关联。SecurityManager
,至少重写这两个checkPermission
方法,这一次它们委托给 SPDP 和/或重写的实现(checkPermission
相应地,它调用当前或提供的访问控制上下文)。对于任何给定的权限,应该咨询哪个(s)和以什么顺序咨询当然取决于实现。当两者都被调用时,应该首先查询 SPDP,因为它的响应速度可能比访问控制上下文快。Policy
,实现implies(ProtectionDomain, Permission)
这样,就像SecurityManager::checkPermission
上面一样,它传递域的一些可理解的表示(通常只有它的CodeSource
) 和权限参数 - 但逻辑上不是SNAA - 到 SPDP。实现应该尽可能高效,因为它会在每个域每个访问控制上下文中调用一次checkPermission
。实例化并安装 ( Policy::setPolicy
) 你的实现。以您认为合适的方式管理身份验证。不幸的是,在这种情况下,主题处理部分并不像创建 a 那样简单ThreadLocal
。
子类化、实例化和安装Policy
执行 和 的组合职责的a SecurityManager::checkPermission
,Policy::implies
如第二种情况中单独描述的。
实例化并安装一个标准SecurityManager
.
创建一个ProtectionDomain
能够存储和公开 SNAA 的子类。
作者1一个DomainCombiner
_
用 SNAA 构建;
实现combine(ProtectionDomain[], ProtectionDomain[])
这样
就像Policy::implies
,实现应该是高效的(例如通过消除重复),因为它会在每次getContext
和checkPermission
AccessController
方法时被调用。
成功验证后,创建一个AccessControlContext
包装当前验证的新文件,以及一个 custom 实例DomainCombiner
,依次包装 SNAA。包装要在调用“内”之外执行的代码AccessController::doPrivilegedWithCombiner
,同时传递替换访问控制上下文。
1 除了使用自定义域和您自己的组合器实现之外,还有一种看似更简单的替代方法,将 SNAA 转换为Principal
s 并使用标准SubjectDomainCombiner
将它们绑定到当前AccessControlContext
的域(如上,或简单地通过Subject::doAs
)。这种方法是否会降低策略的效率主要取决于调用堆栈的深度(访问控制上下文包含多少不同的域)。最终,您认为可以避免作为域组合器的一部分实施的缓存优化会在编写策略时给您带来反击,因此这本质上是您在那时必须做出的设计决策。