Spring Security中的高级授权功能是其如此受欢迎的原因之一。不管你选择使用哪种方式认证,授权服务都可以以一致且简单的方式在应用中被使用。
这一部分将介绍不同的AbstractSecurityInterceptor
实现。然后,继续探讨如何通过使用域访问控制列表来微调授权。
所有的Authentication
实现都存储了GrantedAuthority
对象的列表。这些对象表示当前主体被授予的权限。AuthenticationManager
将GrantedAuthority
对象插入到Authentication
中,之后AccessDecisionManager
会读取这些权限并作出权限决策。
GrantedAuthority
接口只有一个方法:
String getAuthority();
此方法允许AccessDecisionManager
获取以精确的String
形式表示的GrantedAuthority
。大多数AccessDecisionManager
都可以轻松地读取GrantedAuthority
。如果某个GrantedAuthority
不能被字符串精确的表示,那么它就是“复杂的”,那么它的getAuthority
方法应该返回null
。
“复杂”GrantedAuthority
的一个示例是,存储了一系列应用不同用户账户的操作和权限阈值。以一个字符串代表此类授权很困难,所以getAuthority
方法应该返回null
。意味着某个AccessDecisionManager
专门支持这个GrantedAuthority
实现,并能解析其方法的返回值。
SS自带一个GrantedAuthority
实现,即SimpleGrantedAuthority
。它允许任何用户指定的字符串转换成GrantedAuthority
。所有的AuthenticationProvider
都包含使用这个实现来填充Authentication
对象的安全架构。
SS提供了对安全对象(例如,方法执行或者web请求)访问进行控制的拦截器。AccessDecisionManager
执行调用前决策,决定是否允许该调用。
AccessDecisionManager
被AbstractSecurityInterceptor
调用,它负责做出最后的访问控制决策。AccessDecisionManager
包含三个方法:
void decide(Authentication authentication, Object secureObject,
Collection attrs) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
decide
方法接受所有与授权决策相关信息的参数。特别的,安全Object
对象的传递可以检查实际安全对象中包含的参数。例如,假设安全对象是MethodInvocation
,从这个对象中获取任何Customer
参数很容易,然后在AccessDecisionManager
实现某种安全逻辑确保主体被允许操作此Customer
。如果访问被拒绝,实现应该抛出一个AccessDeniedException
。
supports(ConfigAttribute)
方法被AbstractSecurityInterceptor
在一开始调用来决定AccessDecisionManager
是否能处理传入的ConfigAttribute
。supports(Class)
方法被安全拦截器实现调用以确保配置的AccessDecisionManager
支持拦截器中的某种类型的安全对象。
虽然用户可以实现自己的AccessDecisionManager
来控制授权的各个方面,但SS还是提供了若干基于投票的AccessDecisionManager
实现,下图展示了相关的类:
如上,授权决定会轮询一系列的AccessDecisionVoter
实现。AccessDecisionManager
决定是否根据这些投票的评估抛出AccessDeniedException
异常。
接口AccessDecisionVoter
有三个方法:
int vote(Authentication authentication, Object object, Collection attrs);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
具体的实现返回一个int
值,取值为AccessDecisionVoter
中的静态字段ACCESS_ABSTAIN
、ACCESS_DENIED
和ACCESS_GRANTED
。若某实现对授权没有意见可以返回ACCESS_ABSTAIN
,如果有意见则返回其他两个值中的一个。
SS提供了三个具体的AccessDecisionManager
来计算投票。ConsensusBased
实现基于非弃权投票的共识来允许或拒绝访问,在投票相等或者都是弃权的时候也有相关的属性控制行为。AffirmativeBased
实现在有一个或多个ACCESS_GRANTED
票的时候允许访问(即,如果有至少一个同意票,反对票会被忽略),类似ConsensusBased
实现,当所有的票都是弃权票的时候,也有一个参数控制行为。UnanimousBased
实现期望所有投票都为ACCESS_GRANTED
票来允许访问,忽略弃权票,如果有反对票则拒绝访问,像其他实现一样,此实现也有一个参数控制所有票都为弃权票时的行为。
也可以自己实现AccessDecisionManager
来以不同的方式计算投票。例如,特定的AccessDecisionVoter
的投票会有额外的权重,而特定的投票人的反对票有一票否决的作用。
SS中最常用AccessDecisionVoter
实现就是RoleVoter
,它将配置属性当做简单的角色名,当用户有这个角色的时候允许访问。
如果任何ConfigAttribute
以ROLE_
开头,它将投票。如果有一个GrantedAuthority
返回一个String
(通过getAuthority()
方法)完全等于一个或多个以ROLE_
开头的ConfigAttributes
,它将允许访问。 如果没与ROLE_
开头的任何ConfigAttribute
完全匹配,则RoleVoter
将拒绝访问。如果没有ConfigAttribute以ROLE_
开头,则弃权。
另外一个实现是AuthenticatedVoter
,它可以用来区分匿名用户,认证过的用户和remember-me认证的用户。许多站点允许在remember-me身份验证下进行某些有限访问,但需要用户通过登录进行完全访问来确认其身份。
当使用IS_AUTHENTICATED_ANONYMOUSLY
属性来授予匿名访问权限的时候,AuthenticatedVoter
会处理此属性。查阅此类的JavaDoc获取更多信息。
显然你也可以自定义AccessDecisionVoter
,并将自己的访问控制策略添加进去。它可以特定于你的应用(与业务逻辑有关),也可以实现某些安全管理逻辑。例如,例如,可以在Spring网站上找到一篇博客文章,其中介绍了如何使用Voter实时拒绝某些帐户被暂停的用户访问。
AbstractSecurityInterceptor
在处理安全对象调用之前调用AccessDecisionManager
,但某些应用需要修改安全对象调用返回的对象。可以自己很简单地通过AOP关注点来实现此功能,但SS也提供了便利的钩子,它具有几个与其ACL功能集成的具体实现。
下图描述了SS的AfterInvocationManager
及其具体实现:
像SS的其他部分一样,AfterInvocationManager
有一个具体的实现:AfterInvocationProviderManager
,它会轮询AfterInvocationProvider
列表。每个AfterInvocationProvider
都可以修改返回的对象或者抛出AccessDeniedException
异常。实际上,有多个provider可以修改对象,前一个provider的结果被传递到列表中的下一个provider。
注意如果使用AfterInvocationManager
,还需要允许MethodSecurityInterceptor
的AccessDecisionManager
允许某项操作的配置属性。如果使用典型的包含AccessDecisionManager
实现的SS,但没有为特定的安全方法调用定义的配置属性将导致所有的AccessDecisionVoter
都会投弃权票。继续往下,如果AccessDecisionManager
的属性allowIfAllAbstainDecisions
为false
,则会抛出AccessDeniedException
异常。可以设置allowIfAllAbstainDecisions
为true
(通常不建议这么做)或者确保AccessDecisionVoter
至少有一个配置属性能投票并授予权限。后者(也是推荐的方法)通过ROLE_USER
或ROLE_AUTHENTICATED
配置属性来实现。
通常应用中的角色还应该包含其他角色。例如吗,某些应用中有“admin”和“user”的概念,“admin”可以做“user”可以做的任何事情。可以为每个“admin”分配一个“user”角色,或者修改每个需要“user”角色的访问约束为也允许“admin”角色访问。但是如果应用中包含许多不同的角色,将会变得非常复杂。
使用角色分层允许配置角色(或者权限)包含其他角色。SS的RoleVoter的扩展,RoleHierarchyVoter
,配置了一个RoleHierarchy
,从中可以获取用户所分配的所有“reachable authorities”。配置如下:
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy" />
bean>
<bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_ADMIN > ROLE_STAFF
ROLE_STAFF > ROLE_USER
ROLE_USER > ROLE_GUEST
value>
property>
bean>
在这个示例中,有分层的4个角色ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST
。有ROLE_ADMIN
角色的用户,在使用配置了上述的RoleHierarchyVoter
的AccessDecisionManager
进行安全约束评估的时候,就像拥有4个角色一样。>
符号意思是包含。
角色分层提供了一种简便的方法来简化应用的访问控制配置数据并减少需要分配给用户的权限数量。对于更复杂的要求,可以在应用所需的特定访问权限和分配给用户的角色之间定义逻辑映射,在加载用户信息时进行转换。
SS 2.0之前,安全的MethodInvocation
需要大量的模板配置。现在对于方法安全的推荐方式是命名空间配置。这样,方法安全所需的基础bean就会自动配置,因此实际上不需要了解实现类。 这俩经将简要介绍涉及到的类。
方法安全使用MethodSecurityInterceptor
进行,它保护MethodInvocation
。根据配置的方式,拦截器可能特定于单个bean或在多个bean之间共享。拦截器使用MethodSecurityMetadataSource
实例来获取适用于特定方法调用的配置属性。MapBasedMethodSecurityMetadataSource
用于存储以方法名称(可以是通配符)为key配置属性,在使用
或
元素在应用上下文中定义属性时将在内部使用。
也可以直接在应用上下文中配置一个MethodSecurityIterceptor
,以便与Spring AOP的代理机制一起使用:
<bean id="bankManagerSecurity" class=
"org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
<sec:method-security-metadata-source>
<sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
<sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
sec:method-security-metadata-source>
property>
bean>
略
略