THE ROAD AHEAD

ideas for work
随笔 - 3, 文章 - 0, 评论 - 0, 引用 - 0
数据加载中……

(翻译)Spring Security(Part IV)——一般授权概念

Spring Security

参考文档

Part IV. 授权

2.0.x

作者:Ben Alex, Luke Taylor

译者:王耀军(2008.8.8

翻译说明

200888,今晚北京的奥运开幕式即将到来,就在这个下午我终于完成了这份文档的翻译。

Spring Security是个好东西,最近为了新的项目花了些时间来学习,但是网上找不到一个比较好的资料,对授权部分弄不太懂,对着英文版的Reference Documentation,虽然大体能看懂,但是终究理解的不够细致,因此萌发了仔细研读的想法,并籍此机会同时翻译出来,放到网上也好方便后来者。

本文正好与沈哲翻译的Getting Start部分相呼应,一头一尾,中间还有Part II Overall Architecture Part III Authentication,不知道会不会也有机会被翻译共享出来。

由于本人也是在学习,因此难免翻译会有不当之处,如果你发现了,请不吝赐教,我会更正了重新发出。如果你看了本文觉得有帮助,我也很乐意分享你的喜悦。邮件可以发到wangyaojun@gmail.com


Part IV. 授权

具备先进的授权处理能力,是Spring Security之所以如此受欢迎的原因之一。不论你选择如何进行验证——是否使用了Spring Security提供的机制和Provider,或与容器及其它非Spring Security的认证授权机制集成——你会发现可以在你的应用中以一致且简单的方式使用授权服务。

本部分中,我们来研究第一部分中介绍过的AbstractSecurityInterceptor 的不同实现。然后我们看看如何通过域对象的访问控制列表(ACL)来调整授权。

 

一般授权概念

22.1. 权限(Authorities

正如在前面Authentication部分简单提到过的,所有Authentication的实现都必须保存一个GrantedAuthority对象数组。这代表已经授予给principal(相当于用户)的权限。AuthenticationManagerGrantedAuthority对象插入到Authentication对象中,然后由AccessDecisionManager在做授权决定的时候使用。

GrantedAuthority只有一个方法接口:

String getAuthority();

AccessDecisionManager可以通过该方法获取到一个精确代表GrantedAuthority的字符串。通过返回一个代表字符串这样的方式,使得GrantedAuthority可以被大多数AccessDecisionManager“读取”到。如果GrantedAuthority不能精确地用字符串代表,这样的GrantedAuthority被认为是“集合体”,这种情况下getAuthority()必须返回null

 “集合体”GrantedAuthority的一个例子如:一个储存了可以分配给不同用户的操作和授权列表的实现。用字符串来表示这样一个集合体GrantedAuthority会很复杂,因此getAuthority()方法应返回null。这告诉AccessDecisionManager应该对GrantedAuthority实现提供特别支持才能获得具体内容。

Spring Security带有一个GrantedAuthority的实现——GrantedAuthorityImpl。该实现允许把任何用户指定的字符串转换为GrantedAuthority。所有Security Security框架的AuthenticationProvider都是用GrantedAuthorityImpl来填充Authentication对象。

22.2. 调用前处理(Pre-Invocation Handling

AccessDecisionManagerAbstractSecurityInterceptor调用,它负责作出最后的访问控制决定。AccessDecisionManager接口包括了如下3个方法:

void decide(Authentication authentication, Object object, ConfigAttributeDefinition config) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);

从第一个方法可以看出,作出授权评估结果的信息是通过3个参数传递给AccessDecisionManager的。特别是,通过传入安全对象(注:方法中的第二个参数),使得在实际安全对象访问中的参数能被访问到。例如,我们假设安全对象是一个MethodInvocation。可以很容易的获取到MethodInvocation对象,并从中获得用户参数,然后据此在AccessDecisionManager中实现安全逻辑,以确认用户是否被允许进行此操作。如果访问时不被允许的,应该抛出一个AccessDeniedException

supports(ConfigAttribute)AbstractSecurityInterceptor在开始的时候调用,以便确认AccessDecisionManager是否能处理传递过来的ConfigAttribute参数。supports(Class)方法由安全拦截器的实现调用以确认配置的AccessDecisionManager是否支持传递过来的安全对象类型。

用户可以实现自己的AccessDecisionManager来控制授权的所有方面,Spring Security包含了几个基于投票策略的AccessDecisionManager的实现。 22.1, “Voting Decision Manager”给出了相关类的关系图。

Voting Decision Manager

 22.1. Voting Decision Manager

 

通过这种方式,一系列AccessDecisionVoter的实现被带进授权决议中。AccessDecisionManager这些投票者们的投票决定是否允许访问,如果不允许,抛出一个AccessDeniedException

AccessDecisionVoter接口也有3个类似的方法:

int vote(Authentication authentication, Object object, ConfigAttributeDefinition config);
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);

vote方法的具体实现返回一个int值,且必须是AccessDecisionVoter3个静态变量ACCESS_ABSTAIN, ACCESS_DENIED ACCESS_GRANTED之一。如果不能作出任何决定,应该返回ACCESS_ABSTAIN表示弃权。如果能做出决定,则必须是ACCESS_DENIED(拒绝)ACCESS_GRANTED(授权)二者之一。

Spring Security 提供了3个基于投票策略的AccessDecisionManager实现。ConsensusBased基于授权票数与拒绝票数来决定授权与否(不计入弃权票),如果授权数大于拒绝数,则获得授权,否则决绝访问。可以通过设置属性来控制票数相等或全部弃权时的行为。AffirmativeBased的策略是,如果有一个或多个投票者投ACCESS_GRANTED,则获得授权。跟ConsensusBased相似,它也有一个参数可以控制全部投票者弃权时的行为。UnanimousBased仅在全部投ACCESS_GRANTED票的时候才能获得授权,忽略弃权票(即,只要有一个投拒绝票就拒绝)。跟其他实现类似,它也有一个参数可控制在全部弃权情况下的行为。

用户可以实现自己定制的AccessDecisionManager,以不同的方式来进行计票。例如,一个特别的AccessDecisionVoter可能接受附加的权重值,或者其中有一个投票者可以一票否决。

Spring Security提供了两个AccessDecisionVoter的实现。RoleVoter,会对任何一个以“ROLE_”开头的ConfigAttribute进行投票。如果其中有一个GrantedAuthority返回的字符串(通过getAuthority()方法)与ConfigAttributes中一个或多个以“ROLE_”开头的一致,则投授权票。如果没有一致的,则投拒绝票。如果没有一个ConfigAttribute是以“ROLE_”开头的,则投弃权票。RoleVoter在进行一致性对比的时候是大小写敏感的。

BasicAclEntryVoter是另一个Spring Security自带的实现。它与Spring SecurityAclManager(后面会提到)集成。该投票者被设计为在一个应用中可以有多个实例,如:

<bean id="aclContactReadVoter"
    class="org.springframework.security.vote.BasicAclEntryVoter">
  <property name="processConfigAttribute" value="ACL_CONTACT_READ"/>
  <property name="processDomainObjectClass" value="sample.contact.Contact"/>
  <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>
<bean id="aclContactDeleteVoter"
    class="org.springframework.security.vote.BasicAclEntryVoter">
  <property name="processConfigAttribute" value="ACL_CONTACT_DELETE"/>
  <property name="processDomainObjectClass" value="sample.contact.Contact"/>
  <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.DELETE"/>
    </list>
  </property>
</bean>

在上面的例子中,你要定义与MethodSecurityInterceptor AspectJSecurityInterceptor中一些方法对应的ACL_CONTACT_READACL_CONTACT_DELETE。当那些方法被调用的时候,上面定义的相关voter会进行投票。voter会从方法调用中查找第一个类型为sample.contact.Contact的参数,然后把该Contact传递给AclManagerAclManager接着会返回一个应用在当前Authentication上的访问控制列表(ACL)。假设ACL包含有需要的Permissions中的一个,voter即投授权票。如果ACL不含有任何一个voter定义中需要的permissionvolter投拒绝票。BasicAclEntryVoter是一个非常重要的类,它使你能够建立真正复杂的应用,可以完全地控制在应用上下文中定义的域对象的安全。如果你有兴趣了解更多Spring SecurityACL能力,以及如何最好地应用它们,请看本参考指南中的ACL部分和"调用后(After Invocation"部分,然后研究一下Contacts例子应用。

你也可以自己实现定制的AccessDecisionVoterSpring Securityunit test中提供了几个例子,包括ContactSecurityVoterDenyVoterContactSecurityVoterCONTACT_OWNED_BY_CURRENT_USER这个ConfigAttribute找不到的时候投弃权票。如果投票,它会从MethodInvocation中取得Contact对象的owner——即该调用的subject。如果ownerAuthentication中的用户一致,投授权票。它也可以只是简单把Contact ownerAuthentication中的GrantedAuthority进行对比。所有这些只需要简单的几行代码,这充分显示了授权模型的灵活性。

TODO:老的ACL包已经被废弃,去掉这段对老ACL包的描述,改用对新的ACL实现的描述。(即AclEntryVoter)。

22.3. 调用后处理(After Invocation Handling

AccessDecisionManager 是在处理安全对象调用之前被AbstractSecurityInterceptor调用的,而有些应用需要有一条途径可以更改安全对象调用实际返回的对象。你可以很容易的实现自己的AOP concern来达到这个目的,Spring Security提供了一个方便的hook(钩子),同时包括几个具体实现,并与它的ACL能力集成在一起。

 22.2, “After Invocation Implementation” 描述Spring Security AfterInvocationManager及其实现.

After Invocation Implementation

 22.2. After Invocation Implementation

 

Spring Security中很多其它部分一样,AfterInvocationManager有一个单独的具体实现AfterInvocationProviderManager,它带有一个AfterInvocationProvider列表,其中每个AfterInvocationProvider均被允许更改对象或抛出AccessDeniedException。确实是多个provider均能改变对象,列表中的一个provider处理完后把结果提交给下一个provider,下一个provider在这个结果的基础上进行处理。现在让我们来看一看AfterInvocationProviderACL-aware实现。

请注意如果你在使用AfterInvocationManager,你仍需要配置属性以便MethodSecurityInterceptorAccessDecisionManager允许一项操作。如果你在使用Spring Security自带的AccessDecisionManager实现,而没有为安全方法调用配置属性,会导致每个AccessDecisionVoter都弃权。进而,如果AccessDecisionManager"allowIfAllAbstainDecisions"被设置为false,会抛出一个AccessDeniedException。你可以通过如下两个方法避免这个潜在问题:(i)"allowIfAllAbstainDecisions"设为true(尽管这通常是不推荐的);(ii)简单的确认至少配置了一个属性,使AccessDecisionVoter会表决,以授予访问权限。后者(推荐)常常通过配置一个ROLE_USERROLE_AUTHENTICATED来实现。

22.3.1. ACL-AwareAfterInvocationProviders

请注意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许可的用户才应被允许获得它。这种情况AbstractSecurityInterceptor提供的AccessDecisionManager这种方法无法满足。这是因为,在安全对象调用之前,只有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。如果Authentication没有列出的permissions其中之一,provider会抛出AccessDeniedExceptionAclEntryAfterInvocationProviderAclService查询以获得Authentication对该对象访问的ACL

AclEntryAfterInvocationCollectionFilteringProviderAclEntryAfterInvocationProvider相似。它用来从CollectionArray中去除用户没有访问权限的对象。它不抛出AccessDeniedException——只是简单而安静的去除不该被访问的元素。该provider是这样配置的:

<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>

正如你可以想象的,返回的对象是一个Collectionarray,该provider才能进行处理。它会去除任何AclManager指示Authentication没有所需permission之一的元素。

Contacts例子展示了这两个AfterInvocationProvider的应用。

22.3.2. ACL-AwareAfterInvocationProviders (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许可的用户才应被允许获得它。这种情况AbstractSecurityInterceptor提供的AccessDecisionManager这种方法无法满足。这是因为,在安全对象调用之前,只有Contact的标识是可用的。BasicAclAfterInvocationProvider给出了一个解决方案,其配置如下:

<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对象将会被获取并递送给BasicAclEntryAfterInvocationProvider。如果Authentication没有列出的permissions其中之一,provider会抛出AccessDeniedExceptionBasicAclEntryAfterInvocationProviderAclService查询以获得Authentication对该对象访问的ACL

BasicAclEntryAfterInvocationCollectionFilteringProviderBasicAclEntryAfterInvocationProvider相似。它用来从CollectionArray中去除用户没有访问权限的对象。它不抛出AccessDeniedException——只是简单而安静的去除不该被访问的元素。该provider是这样配置的:

 

<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>

正如你可以想象的,返回的对象是一个Collectionarray,该provider才能进行处理。它会去除任何AclManager指示Authentication没有所需permission之一的元素。

Contacts例子展示了这两个AfterInvocationProvider的应用。

22.4. 授权标签库(Authorization Tag Libraries

AuthorizeTag用来在principal有特定的GrantedAuthority时显示body内容。

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

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

如果principal被授予了ROLE_SUPERVISOR,该标签会把标签体内的内容输出。

security:authorize标签声明了如下属性:

·         ifAllGranted:列出的所有roles都必须被授予时显示标签体;

·         ifAnyGranted:列出的任何roles被授予时显示标签体;

·         ifNotGranted:列出的任何一个roles都没有授予时显示标签体;

你应该知道,每个属性中均可列出多个roles,用逗号隔开,里面的空格会被忽略

这个标签库逻辑上在多个属性间用“AND”来处理。这意味着如果你同时给出了两个或多个属性,所有属性均必须为true,标签才会输出标签体。不要在ifNotGranted="ROLE_SUPERVISOR"后又加ifAllGranted="ROLE_SUPERVISOR",否则你会永远见不到你的标签体。

通过要求所有属性必须返回true这种方式,授权标签使你可以创建更加复杂的授权场景。例如,为了防止新supervisor看到内容,你可以在同一个tag中声明ifAllGranted="ROLE_SUPERVISOR"ifNotGranted="ROLE_NEWBIE_SUPERVISOR"。然而,可能简单地使用ifAllGranted="ROLE_EXPERIENCED_SUPERVISOR"比在你的设计中插入“NOT”条件更好。

最后:该tag是以既定顺序来进行评估的,首先是ifNotGranted,然后ifAllGranted,最后是ifAnyGranted

AccessControlListTag用来在当前principalACL,表明可访问域对象时显示内容。

如下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>

如果principal有对"contact"于对象的permission 16permission 1,该tag显示标签体内容。这些数字实际上是BasePermission的位掩码中用到的整数。请参考本参考指南的ACL部分获得更多关于Spring SecurityACL能力的信息。

AclTag是老ACL模块的一部分,应该考虑为被抛弃。因为历史原因,它与AccessControlListTag的工作其实是一样的。

posted on 2008-11-03 20:10 ALGO 阅读(809) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: