验证架构

13.1. 验证

在验证部分简略提过了,所有的Authentication实现需要保存在一个GrantedAuthority对象数组中。 这就是赋予给主体的权限。 GrantedAuthority对象通过AuthenticationManager保存到 Authentication对象里,然后从AccessDecisionManager读出来,进行授权判断。

GrantedAuthority是一个只有一个方法接口:

  String getAuthority();
    

这个方法允许 AccessDecisionManager获得一个精确的 String 来表示 GrantedAuthority。 通过返回的String,一个GrantedAuthority可以简单的用大多数AccessDecisionManager“读取”。 如果GrantedAuthority不能表示为一个StringGrantedAuthority会被看作是“复杂的”,然后返回null

一个“复杂的” GrantedAuthority 的例子会是保存了操作列表和授权信息并应用在不同的客户帐号数值的一个实现。 如果要显示这种复杂的GrantedAuthority,把转换成String是非常复杂的,getAuthority()方法的结果应该返回null。 这会展示给任何AccessDecisionManager,它需要特别支持GrantedAuthority的实现,来了解它的内容。

Spring Security包含一个具体的 GrantedAuthority 实现,GrantedAuthorityImpl。 这允许任何用户指定的String转换成GrantedAuthority。 所有AuthenticationProvider包含安全结构,它使用GrantedAuthorityImpl组装Authentication对象。

13.2. 处理预调用

像我们在技术概述一章看到的那样,Spring Security提供了一些拦截器,来控制对安全对象的访问权限,例如方法调用或web请求。 一个是否允许执行调用的预调用决定,是由AccessDecisionManager实现的。

13.2.1. AccessDecisionManager

这个 AccessDecisionManagerAbstractSecurityInterceptor调用,它用来作最终访问控制的决定。 这个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是否可以执行传递ConfigAttributesupports(Class)方法被安全拦截器实现调用,包含安全拦截器将显示的AccessDecisionManager支持安全对象的类型。

13.2.2. 基于投票的AccessDecisionManager实现

虽然用户可以实现它自己的AccessDecisionManager来控制所有授权的方面,Spring Security包括很多基于投票的AccessDecisionManager实现。 Figure 13.1, “投票决议管理器”显示有关的类。

投票决议管理器

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_ABSTAINACCESS_DENIEDACCESS_GRANTED。 如果一个投票实现没有选择授权决定,会返回ACCESS_ABSTAIN。 如果它进行过抉择,它必须返回ACCESS_DENIEDACCESS_GRANTED

这儿有三个由Spring Security提供的具体 AccessDecisionManager,可以进行投票。 ConsensusBased实现会授权,或拒绝访问,基于没有放弃的那些投票的共识。 那些属性在平等投票时间上被提供来控制行为,或如果所有投票都是弃权了。 AffirmativeBased实现会授予访问权限,如果收到一个或多个ACCESS_GRANTED投票(比如,一个反对投票会被忽略,如果这里至少有一个赞成票)。 像ConsensusBased实现一样,这里有一个参数控制如果所有投票都弃权的行为。 UnanimousBased提供器希望一致的ACCESS_GRANTED,来允许访问,忽略弃权。 如果这里有任何一个ACCESS_DENIED投票,它会拒绝访问。 像其他实现一样,有一个参数控制如果所有投票都弃权的行为。

有可能实现一个自定义的AccessDecisionManager进行不同的投票统计。 比如,投票一个特定的AccessDecisionVoter可能获得更多的权重,这样一个拒绝票对特定的投票者可能有否决权的效果。

13.2.2.1. RoleVoter

Spring Security中最常用到的AccessDecisionVoter实现是简单的RoleVoter,它把简单的角色名称作为配置属性,如果用户分配了某个角色就被允许访问。

如果有任何一个配置属性是以ROLE_开头的,就可以进行投票。 如果GrantedAuthority返回的String内容(通过getAuthority()方法),与一个或多个以ROLE_开头的ConfigAttributes完全相同的话,就表示允许访问。 如果没有匹配任何一个以ROLE_开头的ConfigAttributeRoleVoter就会拒绝访问。 如果没有以ROLE_开头的ConfigAttribute,投票者就会弃权。 RoleVoter在匹配的时候是大小写敏感的,这也包括对 ROLE_这个前缀。

13.2.2.2. AuthenticatedVoter

另一个表决器我们不直接看到的是AuthenticatedVoter, 它可以被用在不同的场景下,比如匿名,完全授权和remember-me认证用户。 很多网站允许在rememberMe认证情况下访问有限的资源,但是需要用户验证 自己的身份通过登录来获得完全访问的权限。

什么时候我们使用IS_AUTHENTICATED_ANONYMOUSLY来授权匿名访问呢, 这个属性被AuthenticatedVoter处理,参考这个类的javadoc 获得更多信息。

13.2.2.3. Custom Voters

也可能实现一个自定义的AccessDecisionVoter。 Spring Security的单元测试提供了很多例子,包括ContactSecurityVoterDenyVoter。 如果CONTACT_OWNED_BY_CURRENT_USERConfigAttribute没有找到,ContactSecurityVoter在投票决定的时候就会弃权。 如果投票,它通过MethodInvocation来确认Contact对象的主体,这是方法调用的主体。 如果Contact主体匹配Authentication对象中表现的主体,它就会投赞成票。 它可能对Contact主体有一个简单的比较,使用一些Authentication表现的GrantedAuthority。 所有这些对应不多几行代码,演示授权模型的灵活性。

13.3. 处理后决定

虽然 AccessDecisionManagerAbstractSecurityInterceptor在执行安全方法调用之前调用,一些程序需要一个方法来修改安全对象调用返回的对象。 虽然你可以简单实现自己的AOP涉及实现,Spring Security提供有许多具体实现方面调用,集成它的ACL功能。

Figure 13.2, “后决定实现” 展示Spring Security的 AfterInvocationManager 和它的具体实现。

后决定实现

Figure 13.2. 后决定实现


就像Spring Security的其他很多部分一样,AfterInvocationManager有一个单独的具体实现,AfterInvocationProviderManagerAfterInvocationProvider列表。 每个AfterInvocationProvider被允许修改返回对象,或抛出AccessDeniedException异常。 确实多个提供器可以修改对象,作为之前提供器的结果传递给队列的下一个。 让我们现在考虑我们的AfterInvocationProvider的ACL提醒实现。

请注意,如果你使用 AfterInvocationManager,你将依然需要配置属性,让MethodSecurityInterceptorAccessDecisionManager允许一个操作。 如果你使用典型的Spring Security包含AccessDecisionManager实现,没有在特定的安全方法调用上配置属性定义,会导致AccessDecisionVoter弃权。 这里,如果AccessDecisionManager的"allowIfAllAbstainDecisions"属性是false,会抛出一个AccessDeniedException异常。 你可以避免这个潜在的问题,使用(i)把"allowIfAllAbstainDecisions" 设置为true(虽然通常不建议这么做),或者(ii)直接确保这里至少有一个配置属性,这样AccessDecisionVoter会投赞成票。 后者(推荐)方法通常使用ROLE_USERROLE_AUTHENTICATED配置属性。