在验证部分简略提过了,所有的Authentication
实现需要保存在一个GrantedAuthority
对象数组中。
这就是赋予给主体的权限。
GrantedAuthority
对象通过AuthenticationManager
保存到 Authentication
对象里,然后从AccessDecisionManager
读出来,进行授权判断。
GrantedAuthority
是一个只有一个方法接口:
String getAuthority();
这个方法允许 AccessDecisionManager
获得一个精确的 String
来表示 GrantedAuthority
。
通过返回的String
,一个GrantedAuthority
可以简单的用大多数AccessDecisionManager
“读取”。
如果GrantedAuthority
不能表示为一个String
,GrantedAuthority
会被看作是“复杂的”,然后返回null
。
一个“复杂的” GrantedAuthority
的例子会是保存了操作列表和授权信息并应用在不同的客户帐号数值的一个实现。
如果要显示这种复杂的GrantedAuthority
,把转换成String
是非常复杂的,getAuthority()
方法的结果应该返回null
。
这会展示给任何AccessDecisionManager
,它需要特别支持GrantedAuthority
的实现,来了解它的内容。
Spring Security包含一个具体的 GrantedAuthority
实现,GrantedAuthorityImpl
。
这允许任何用户指定的String
转换成GrantedAuthority
。
所有AuthenticationProvider
包含安全结构,它使用GrantedAuthorityImpl
组装Authentication
对象。
像我们在技术概述一章看到的那样,Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。
一个是否允许执行调用的预调用决定,是由AccessDecisionManager
实现的。
这个 AccessDecisionManager
被AbstractSecurityInterceptor
调用,它用来作最终访问控制的决定。
这个AccessDecisionManager
接口包含三个方法:
void decide(Authentication authentication, Object secureObject, List<ConfigAttributeDefinition> config) throws AccessDeniedException; boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
从第一个方法可以看出来,AccessDecisionManager
使用方法参数传递所有信息,这好像在认证评估时进行决定。
特别是,在真实的安全方法期望调用的时候,传递安全Object
启用那些参数。
比如,让我们假设安全对象是一个MethodInvocation
。
很容易为任何Customer
参数查询MethodInvocation
,,然后在AccessDecisionManager
里实现一些有序的安全逻辑,来确认主体是否允许在那个客户上操作。
如果访问被拒绝,实现将抛出一个AccessDeniedException
异常。
这个 supports(ConfigAttribute)
方法在启动的时候被AbstractSecurityInterceptor
调用,来决定AccessDecisionManager
是否可以执行传递ConfigAttribute
。
supports(Class)
方法被安全拦截器实现调用,包含安全拦截器将显示的AccessDecisionManager
支持安全对象的类型。
虽然用户可以实现它自己的AccessDecisionManager
来控制所有授权的方面,Spring Security包括很多基于投票的AccessDecisionManager
实现。
Figure 13.1, “投票决议管理器”显示有关的类。
使用这种方法,一系列的 AccessDecisionVoter
实现为授权做决定。
这个AccessDecisionManager
会决定是否基于它的投票评估抛出AccessDeniedException
异常。
AccessDecisionVoter
接口有三个方法:
int vote(Authentication authentication, Object object, List<ConfigAttributeDefinition> config); boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
具体实现返回一个int
,使用可能的反映的AccessDecisionVoter
静态属性ACCESS_ABSTAIN
,ACCESS_DENIED
和ACCESS_GRANTED
。
如果一个投票实现没有选择授权决定,会返回ACCESS_ABSTAIN
。
如果它进行过抉择,它必须返回ACCESS_DENIED
或ACCESS_GRANTED
。
这儿有三个由Spring Security提供的具体 AccessDecisionManager
,可以进行投票。
ConsensusBased
实现会授权,或拒绝访问,基于没有放弃的那些投票的共识。
那些属性在平等投票时间上被提供来控制行为,或如果所有投票都是弃权了。
AffirmativeBased
实现会授予访问权限,如果收到一个或多个ACCESS_GRANTED
投票(比如,一个反对投票会被忽略,如果这里至少有一个赞成票)。
像ConsensusBased
实现一样,这里有一个参数控制如果所有投票都弃权的行为。
UnanimousBased
提供器希望一致的ACCESS_GRANTED
,来允许访问,忽略弃权。
如果这里有任何一个ACCESS_DENIED
投票,它会拒绝访问。
像其他实现一样,有一个参数控制如果所有投票都弃权的行为。
有可能实现一个自定义的AccessDecisionManager
进行不同的投票统计。
比如,投票一个特定的AccessDecisionVoter
可能获得更多的权重,这样一个拒绝票对特定的投票者可能有否决权的效果。
Spring Security中最常用到的AccessDecisionVoter
实现是简单的RoleVoter
,它把简单的角色名称作为配置属性,如果用户分配了某个角色就被允许访问。
如果有任何一个配置属性是以ROLE_
开头的,就可以进行投票。
如果GrantedAuthority
返回的String
内容(通过getAuthority()
方法),与一个或多个以ROLE_
开头的ConfigAttributes
完全相同的话,就表示允许访问。
如果没有匹配任何一个以ROLE_
开头的ConfigAttribute
,RoleVoter
就会拒绝访问。
如果没有以ROLE_
开头的ConfigAttribute
,投票者就会弃权。
RoleVoter
在匹配的时候是大小写敏感的,这也包括对 ROLE_
这个前缀。
另一个表决器我们不直接看到的是AuthenticatedVoter
,
它可以被用在不同的场景下,比如匿名,完全授权和remember-me认证用户。
很多网站允许在rememberMe认证情况下访问有限的资源,但是需要用户验证
自己的身份通过登录来获得完全访问的权限。
什么时候我们使用IS_AUTHENTICATED_ANONYMOUSLY
来授权匿名访问呢,
这个属性被AuthenticatedVoter
处理,参考这个类的javadoc
获得更多信息。
也可能实现一个自定义的AccessDecisionVoter
。
Spring Security的单元测试提供了很多例子,包括ContactSecurityVoter
和DenyVoter
。
如果CONTACT_OWNED_BY_CURRENT_USER
的ConfigAttribute
没有找到,ContactSecurityVoter
在投票决定的时候就会弃权。
如果投票,它通过MethodInvocation
来确认Contact
对象的主体,这是方法调用的主体。
如果Contact
主体匹配Authentication
对象中表现的主体,它就会投赞成票。
它可能对Contact
主体有一个简单的比较,使用一些Authentication
表现的GrantedAuthority
。
所有这些对应不多几行代码,演示授权模型的灵活性。
虽然 AccessDecisionManager
被AbstractSecurityInterceptor
在执行安全方法调用之前调用,一些程序需要一个方法来修改安全对象调用返回的对象。
虽然你可以简单实现自己的AOP涉及实现,Spring Security提供有许多具体实现方面调用,集成它的ACL功能。
Figure 13.2, “后决定实现” 展示Spring Security的 AfterInvocationManager
和它的具体实现。
就像Spring Security的其他很多部分一样,AfterInvocationManager
有一个单独的具体实现,AfterInvocationProviderManager
有AfterInvocationProvider
列表。
每个AfterInvocationProvider
被允许修改返回对象,或抛出AccessDeniedException
异常。
确实多个提供器可以修改对象,作为之前提供器的结果传递给队列的下一个。
让我们现在考虑我们的AfterInvocationProvider
的ACL提醒实现。
请注意,如果你使用 AfterInvocationManager
,你将依然需要配置属性,让MethodSecurityInterceptor
的AccessDecisionManager
允许一个操作。
如果你使用典型的Spring Security包含AccessDecisionManager
实现,没有在特定的安全方法调用上配置属性定义,会导致AccessDecisionVoter
弃权。
这里,如果AccessDecisionManager
的"allowIfAllAbstainDecisions
"属性是false
,会抛出一个AccessDeniedException
异常。
你可以避免这个潜在的问题,使用(i)把"allowIfAllAbstainDecisions
" 设置为true
(虽然通常不建议这么做),或者(ii)直接确保这里至少有一个配置属性,这样AccessDecisionVoter
会投赞成票。
后者(推荐)方法通常使用ROLE_USER
或ROLE_AUTHENTICATED
配置属性。