为了解决应用程序权限。Seam安全提供了一个可扩展的框架。下面的类图显式了权限框架的主要组件的概览:

相关的类将在接下来的章节中进行详细的解释。
这实际是一个接口,它提供了解决个别对象权限的方法。Seam提供了下列内建的PermissionResolver
实现,下面章节将详细描述它们:
RuleBasedPermissionResolver
- 这个权限解析器使用Drools来解决基于规则的许可检查。PersistentPermissionResolver
- 这个权限解析器存储对象的权限在一个持久化存储器中,比如关系型数据库。
实现你自己的权限解析器是非常简单的。PermissionResolver
接口只定义了两个必须实现的方法,如下表中显示的那样。在你的Seam项目中发布你自己的PermissionResolver
,在部署期间会自己扫描到它并使用默认的ResolverChain
注册。
返回类型 | 方法 | 说明 |
---|---|---|
| | 这个方法必须解决当前已验证用户(通过调用 |
| | 这个方法将从指定的集合中移除任何对象,如果 |
注意
由于他们是缓存在用户的会话,任何定制PermissionResolver实现必须遵守几个限制。第一,它们也许不包含任何比回话范围更细粒度的状态 (并且组件本身的范围应该是应用和回话)。第二,它们不能使用依赖注入,因为它们可能从多个线程同时访问。实际上从性能上考虑,推荐使用@BypassInterceptors
注释来完全绕过Seam的拦截器栈。
一个ResolverChain
包含一个有顺序的以解决对于一个特别对象类或者权限目标的对象权限的PermissionResolver
的列表。
缺省的ResolverChain
由在应用程序部署期间发现的所有权限解析器组成。当创建缺省的ResolverChain
时,会触发org.jboss.seam.security.defaultResolverChainCreated
事件(传递ResolverChain
实例作为事件的参数)。它还允许添加额为的那些由于某些原因没有在发布期间发现的解析器,或者对重新对列表中的解析器进行排序和移除某个解析器。
下面这个序列图显示了权限框架中的组件之间在许可检查过程中的交互。一个许可检查可以源自好几个地方,例如:安全拦截器、s:hasPermission
EL函数、或者通过API调用Identity.checkPermission
:

1. 启动一个许可检查某些地方(代码或通过一个EL表达式)导致调用
Identity.hasPermission()
。1.1.
Identity
调用PermissionMapper.resolvePermission()
,传递被解析的权限作为参数。1.1.1.
PermissionMapper
包含一个关于ResolverChain
实例,以类为键值的Map
。它使用这个映射来为权限的目标对象定位正确的ResolverChain
。一旦它有一个正确的ResolverChain
,它通过调用ResolverChain.getResolvers()
取回PermissionResolver
列表。1.1.2. 遍历
ResolverChain
中的PermissionResolver
,PermissionMapper
调用它的hasPermission()
方法,传递被检查的权限实例作为参数,然后成功的通过许可检查,PermissionMapper
返回true
给Identity
。如果PermissionResolver
返回值没有true,许可检查失败。
Seam提供的内建权限解析器之一,RuleBasedPermissionResolver
允许基于一套Drools安全规则来评估权限。使用规则引擎有以下这些优点 1)用来评估用户权限的业务逻辑的集中位置,2)快速──Drools使用非常有效的算法来评估大量的包括多条件的复杂规则。
如果使用Seam安全提供的基于规则的权限特性,随你项目需要发布以下Drools需要的jar包:
drools-compiler.jar
drools-core.jar
janino.jar
antlr-runtime.jar
mvel14.jar
RuleBasedPermissionResolver
首先需要在components.xml
中配置一个Drools规则库。缺省情况下,规则库名称为securityRules
,如下示例:
xmlns:core ="http://jboss.com/products/seam/core"
xmlns:security ="http://jboss.com/products/seam/security"
xmlns:drools ="http://jboss.com/products/seam/drools"
xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation =
"http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd
http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd
http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.1.xsd"
http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.1.xsd" >
< drools:rule-base name ="securityRules" >
< drools:rule-files >
< value > /META-INF/security.drl </ value >
</ drools:rule-files >
</ drools:rule-base >
</ components >
缺省的规则库名称可以通过指定RuleBasedPermissionResolver
的security-rules
属性来重载:
一旦配置好规则库(RuleBase
)组件,就可以开始编写安全规则了。
编写安全规则的第一步是在你应用程序的jar文件的/META-INF
目录的创建一个新的规则文件。通常这个文件像security.drl
这样命名,然而你也可以用任何你喜欢的名字命名只要在components.xml
中有对应的配置。
安全规则文件应该包含些什么呢?这时至少浏览一下Drools文档是个不错的注意,不管怎样,先从一个极简单的例子开始:
import org.jboss.seam.security.permission.PermissionCheck;
import org.jboss.seam.security.Role;
rule CanUserDeleteCustomers
when
c: PermissionCheck(target == " customer " , action == " delete " )
Role(name == " admin " )
then
c.grant();
end
让我们一步一步来看。我们首先看到的是包声明。在Drools中包本质上是一个规则的集合。这个包名你可以任意取──它不涉及规则基范围之外的任何东西。
接下来,我们注意到一些PermissionCheck
和Role
类的导入语句。这些导入信息告诉规则引擎在我们的规则中将引用这些类。
最后,我完成规则的代码。每一个在包内的规则应该取一个唯一的名字(通常是规则目的的描述)。在这个例子中,我们的规则叫做CanUserDeleteCustomers
,它用来检查一个用户是否允许删除一个客户记录。
从规则定义的主体来看,我们可以看到到两个截然不同的部分。规则有称之为左手边(LHS)和右手边(RHS)两部分。LHS由规则的条件部分组成,如:一列必须满足才能激活规则的条件。LHS用when
表现。RHS是结果,或者只有满足LHS中的所有条件才能机会的规则中的动作部分。RHS用then
表现。规则结尾用end
表示。
如果我们看规则的LHS部分,能看到列出了两个条件。我们先看看第一个条件:
这个条件标明工作内存中必须存在一个target
属性等于“customer”,并且action
属性等于“delete”的PermissionCheck
对象。
那么,什么是工作内存(working memory)?在Drools的术语中也称为“有状态会话(stateful session)”,工作内存是一个保存上下文信息会话范围对象,那些上下文信息是规则引擎做出许可检查的决定时必须的。每次hasPermission()
方法被调用,一个临时的PermissionCheck
对象,或者Fact,被插入工作内存。PermissionCheck
和要被检查的权限完全一致,因此如果你调用hasPermission("account", "create")
那么在许可检查期间一个target
等于“account”,action
等于“create”的PermissionCheck
对象将被插入工作内存。
除了PermissionCheck
facts,也有一个为验证用户是其中成员之一的每一个角色的org.jboss.seam.security.Role
fact。这些Role
facts是和在每一次许可检查开始使的用户的已验证角色同步的。结果是,如果一个已验证用户不是角色的成员之一,任何在许可检查期间插入工作内存的Role
对象都将在下一次许可检查发生前删除。除了PermissionCheck
和Role
facts,工作内存也保存了验证过程产生的java.security.Principal
对象。
通过调用RuleBasedPermissionResolver.instance().getSecurityContext().insert()
方法,传递一个对象作为参数,还可以把这个对象作为一个额外的长期的fact插入到工作内存中。对于已讨论过得在每次许可检查开始时同步的Role
对象是个例外。
回到我们简单例子中,我们会注意到LHS的第一行有一个c:
前缀。这是一个变量绑定,用来引用匹配条件(本例中是PermissionCheck
)后返回的对象。再看LHS的第二行,我们看见:
这个条件简单的说明工作内存中必须存在一个名为“admin”的Role
对象。前面已经提到,用户角色在每一次许可检查的开始被插入工作内存。因此,这俩个条件本质上在说“如果你正在检查customer:delete
权限并且用户是”admin“角色的成员之一,将激活规则”。
那么激活规则的结果是什么呢?让我们看看规则的RHS:
RHS由Java代码组成,在本例中调用了c
对象的grant()
方法,c
对象已经在为PermissionCheck
对象的变量绑定中提到了。除了PermissionCheck
对象的name
和action
属性,还有一个granted
属性被初始化为false
。调用PermissionCheck
的grant()
方法设置granted
属性为true
,这意味着许可检查是成功的,允许用户运行任何想要做许可检查的动作。
到目前为止,我们仅仅看了对字符串权限目标的检查。为更复杂类型的权限目标写安全规则当然也是可以的。比如,假设你希望写一个安全规则来允许你的用户创建blog评论。下面的规则演示了如何表达在需要MemberBlog
的实例作为许可检查的目标,并且需要当前已验证用户是user
角色的成员之一情况下的规则写法:
no - loop
activation - group " permissions "
when
blog: MemberBlog()
check: PermissionCheck(target == blog, action == " create " , granted == false )
Role(name == " user " )
then
check.grant();
end
通过子你的规则中忽略PermissionCheck
的action
限制, 实现一个通配符许可检查(允许针对一个给定的许可目标的所有动作)也是可能的。像这样:
when
c: PermissionCheck(target == " customer " )
Role(name == " admin " )
then
c.grant();
end;
这个规则允许具有admin
角色的用户执行任何customer
许可检查的任何动作。