(通译)Spring Security-2.0.x参考文档“通用授权概念”

(翻译)Spring Security-2.0.x参考文档“通用授权概念”
通用授权概念
22.1. 授权

在认证部分简略提过了,所有的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对象。
22.2. 处理预调用

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

这个 AccessDecisionManager 被AbstractSecurityInterceptor调用,它用来作最终访问控制的决定。 这个AccessDecisionManager接口包含三个方法:

void decide(Authentication authentication, Object secureObject, 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支持安全对象的类型。
22.2.1.1. 基于投票的AccessDecisionManager实现

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

Figure 22.1. 投票决议管理器

使用这种方法,一系列的 AccessDecisionVoter 实现为授权做决定。 这个AccessDecisionManager会决定是否基于它的投票评估抛出AccessDeniedException异常。

AccessDecisionVoter 接口有三个方法:

int vote(Authentication authentication, Object object, 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可能获得更多的权重,这样一个拒绝票对特定的投票者可能有否决权的效果。
22.2.1.1.1. RoleVoter

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

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

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

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

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

Figure 22.2. 后决定实现


就像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配置属性。
22.3.1. ACL提醒后决议提供器

请注意:Acegi Security 1.0.3包含一个新ACL模型预览。 新ACL模型特别重写了以前的ACl模块。 新模块可以在org.springframework.security.acls包里找到,老ACL模块放在org.springframework.security.acl。 我们推荐用户考虑测试一下新ACL模块,使用它建立程序。 老ACL模型应该考虑废弃了,可能在未来版本中删除。 下面的信息是关于新ACL包的,因此也是推荐的。

我们经常在服务层写的方法就像这样:

public Contact getById(Integer id);

常常,只有主体允许读取Contact,就应该允许获得它。 在这情况,AccessDecisionManager方法由AbstractSecurityInterceptor提供将不够了。 这是因为Contact的身份,在调用安全对象之前是完全有效地。 AclAfterInvocationProvider提供了一个解决方法,像下面这样配置:

<bean id="afterAclRead"
   class="org.springframework.security.afterinvocation.AclEntryAfterInvocationProvider">
  <constructor-arg ref="aclService"/>
  <constructor-arg>
    <list>
      <ref local="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
      <ref local="org.springframework.security.acls.domain.BasePermission.READ"/>
    </list>
  </constructor-arg>
</bean>

在上面的例子里,Contact需要重新审核,并传递给AclEntryAfterInvocationProvider。 提供器会抛出AccessDeniedException异常,如果requirePermission列表的其中一个没有放在Authentication里。 AclEntryAfterInvocationProvider查询Acl 服务,为Authentication对应的领域对象决定应用ACL。

一个简单的 AclEntryAfterInvocationProvider 是 AclEntryAfterInvocationCollectionFilteringProvider。 它用来从主体不允许访问的情况删除Collection或元素数据,。 它永远不会抛出AccessDeniedException - 直接安静的删除不允许的元素。 提供器如下配置:

<bean id="afterAclCollectionRead"
    class="org.springframework.security.afterinvocation.AclEntryAfterInvocationCollectionFilteringProvider">
  <constructor-arg ref="aclService"/>
  <constructor-arg>
    <list>
      <ref local="org.springframework.security.acls.domain.BasePermission.ADMINISTRATION"/>
      <ref local="org.springframework.security.acls.domain.BasePermission.READ"/>
    </list>
  </constructor-arg>
</bean>
   

像你可以想像的,返回的Object必须是让提供器可以操作的Collection或数组。 如果AclManager分析Authentication没有获得一个队列中的requirePermission,它就会删除元素。

Contacts实例程序演示这两个 AfterInvocationProvider。
22.3.2. ACL-Aware AfterInvocationProviders (老 ACL 模块)

请注意:Acegi Security 1.0.3包含一个新ACL模型预览。 新ACL模型显著的重写了以前的ACl模块。 新模块可以在org.springframework.security.acls包里找到,老ACL模块放在org.springframework.security.acl。 我们推荐用户考虑测试一下新ACL模块,使用它建立程序。 老ACL模型应该考虑废弃了,可能在未来版本中删除。

常用服务层方法,我们所有写到一个状态或另一个像这样:

public Contact getById(Integer id);

常常,只有主体允许读取Contact,应该允许获得它。 在这情况,AccessDecisionManager方法由AbstractSecurityInterceptor提供将不够了。 这是因为Contact的身份,在调用安全对象之前是完全有效地。 AclAfterInvocationProvider提供了一个解决方法,它像下面这样配置:

public Contact getById(Integer id);

常常,只有主体允许读取Contact,应该允许获得它。 在这情况,AccessDecisionManager方法由AbstractSecurityInterceptor提供将不够了。 这是因为Contact的身份,在调用安全对象之前是完全有效地。 AclAfterInvocationProvider提供了一个解决方法,它像下面这样配置:

<bean id="afterAclRead"
    class="org.springframework.security.afterinvocation.BasicAclEntryAfterInvocationProvider">
  <property name="aclManager" ref="aclManager"/>
  <property name="requirePermission">
    <list>
      <ref local="org.springframework.security.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
      <ref local="org.springframework.security.acl.basic.SimpleAclEntry.READ"/>
    </list>
  </property>
</bean>
     

在上面的例子里,Contact需要重新审核,并传递给AclEntryAfterInvocationProvider。 提供器会抛出AccessDeniedException异常,如果requirePermission列表的其中一个没有放在Authentication里。 AclEntryAfterInvocationProvider查询Acl 服务,为Authentication对应的领域对象决定应用ACL。

一个简单的 AclEntryAfterInvocationProvider 是 AclEntryAfterInvocationCollectionFilteringProvider。 它用来从主体不允许访问的情况删除Collection或元素数据。 它永远不会抛出AccessDeniedException - 直接安静的删除不允许的元素。 提供器如下配置:

<bean id="afterAclCollectionRead"
    class="org.springframework.security.afterinvocation.BasicAclEntryAfterInvocationCollectionFilteringProvider">
  <property name="aclManager" ref="aclManager"/>
  <property name="requirePermission">
    <list>
      <ref local="org.springframework.security.acl.basic.SimpleAclEntry.ADMINISTRATION"/>
      <ref local="org.springframework.security.acl.basic.SimpleAclEntry.READ"/>
    </list>
  </property>
</bean>

像你可以想像的,返回的Object必须是Collection或数组,让提供器可以操作。 它会删除元素,如果AclManager分析Authentication没有获得一个队列中的requirePermission。

Contacts实例程序演示这两个 AfterInvocationProvider。
22.4. 授权标签库

AuthorizeTag用来包括内容,如果当前主体拥有确定的GrantedAuthority。

下面的JSP片段展示如何使用 AuthorizeTag:

<security:authorize ifAllGranted="ROLE_SUPERVISOR">
<td>
  <a href="del.htm?id=<c:out value="${contact.id}"/>">Del</a>
</td>
</security:authorize>

如果主体拥有ROLE_SUPERVISOR权限,这个标签就会显示标签的内容A。

security:authorize 标签声明下面的属性:

    *

      ifAllGranted:这个标签列出的所有角色必须授权,才能输出标签的内容。
    *

      ifAnyGranted:任何一个这个标签列出的角色必须授权,才能输出标签的内容。
    *

      ifNotGranted:这个标签列出的角色没有一个是授权的,才能输出标签的内容。

你会注意到,每个属性你可以使用多个角色。 简单使用逗号来分隔角色。 这个authorize标签忽略属性之间的空格。

标签库逻辑和所有它的参数一起。 这意味着如果你结合两个或多个属性,这个标签所有属性必须是true,才会输出内容。 不要在ifNotGranted="ROLE_SUPERVISOR"后面添加 ifAllGranted="ROLE_SUPERVISOR",否则你会奇怪为什么总也看不到标签的内容。

如果想要所有的属性都返回true,授权标签允许你创建更复杂的授权场景。 比如,你可以声明ifAllGranted="ROLE_SUPERVISOR",并在同一个标签里使用ifNotGranted="ROLE_NEWBIE_SUPERVISOR",可以防止新的超级用户看到标签内容。 然而你无疑可以直接使用ifAllGranted="ROLE_EXPERIENCED_SUPERVISOR",而不是在你的设计里添加NOT条件。

最后一件事:标签验证授权有特定的顺序:首先是ifNotGranted,然后是ifAllGranted,最后是if AnyGranted。

AccessControlListTag用来包含内容,判断当前的主体是否有一个ACL来显示领域对象。

下面的JSP片段,演示如何使用 AccessControlListTag:

<security:accesscontrollist domainObject="${contact}" hasPermission="8,16">
<td><a href="<c:url value="del.htm"><c:param name="contactId" value="${contact.id}"/></c:url>">Del</a></td>
</security:accesscontrollist>

这标签会导致标签内容显示出来,如果主体拥有对于"contact"领域对象授权16或者授权1。 这个数值其实是证书,使用BasePermission位掩码。 请参考参考指南的ACL部分,了解更多Spring Security的ACL知识。

AclTag 是老ACL模型的一部分,应该考虑废弃。因为一些历史问题,它与AccessControlListTag的作用相同。