在Spring Security的默认过滤器链中,最后一个servelt过滤器是FilterSecurityInterceptor,它的作用是判断一个特定的请求是被允许还是被拒绝。在FilterSecurityInterceptor被触发的时候,安全实体已经经过了认证,所以系统知道他们是合法的用户。(其实也有可能是匿名的用户,译者注)。请记住的一点是,Authentication提供了一个方法((List<GrantedAuthority>
getAuthorities()),将会返回当前安全实体的一系列权限列表。授权的过程将使用这个方法提供的信息来决定一个特定的请求是否会被允许。
需要记住的是授权是一个二进制的决策——一个用户要么有要么没有访问一个受保护资源的权限。在授权中,没有模棱两可的情景。
在Spring Security中,良好的面向对象设计随处可见,在授权决策管理中也不例外。回忆一下我们在本章前面的讨论,一个名为访问控制决策器(access decision manager)的组件负责作出授权决策。
在Spring Security中,o.s.s.access.AccessDecisionManager接口定义了两个简单而合理的方法,它们能够用于请求的决策判断流程:
<!--[if !supportLists]-->l <!--[endif]-->supports:这个逻辑操作实际上包含两个方法,它们允许AccessDecisionManager的实现类判断是否支持当前的请求。
<!--[if !supportLists]-->l <!--[endif]-->decide:基于请求的上下文和安全配置,允许AccessDecisionManager去核实访问是否被允许以及请求是否能够被接受。decide方法实际上没有返回值,通过抛出异常来表明对请求访问的拒绝。
与AuthenticationException及其子类在认证过程中的使用很类似,特定类型的异常能够表明应用在授权决策中的不同处理结果。o.s.s.access.AccessDeniedException是在授权领域里最常见的异常,因此值得过滤器链进行特殊的处理。我们将在第六章中详细介绍它的高级配置。
AccessDecisionManager是能够通过标准的Spring bean绑定和引用实现完全的自定义配置。AccessDecisionManager的默认实现提供了一个基于AccessDecisionVoter接口和投票集合的授权机制。
投票器(voter)是在授权过程中的一个重要角色,它的作用是评估以下的内容:
<!--[if !supportLists]-->l <!--[endif]-->要访问受保护资源的请求所对应上下文(如URL请求的IP地址);
<!--[if !supportLists]-->l <!--[endif]-->用户的凭证信息(如果存在的话);
<!--[if !supportLists]-->l <!--[endif]-->要试图访问的受保护资源;
<!--[if !supportLists]-->l <!--[endif]-->系统的配置以及要访问资源本身的配置参数。
AccessDecisionManager还会负责传递要请求资源的访问声明信息(在代码中为ConfigAttribute接口的实现类)给投票器。在web URL的请求中,投票器将会得到资源的访问声明信息。如果看一下我们配置文件中非常基础的拦截声明,我们能够看到ROLE_USER被设置为访问配置并用于用户试图访问的资源:
<intercept-url pattern="/*" access="ROLE_USER"/> |
投票器将会对用户是否能够访问指定的资源做出一个判断。Spring Security允许过滤器在三种决策结果中做出一种选择,它们的逻辑定义在o.s.s.access.AccessDecisionVoter接口中通过常量进行了定义。
决策类型 |
描述 |
Grant (ACCESS_GRANTED) |
投票器允许对资源的访问 |
Deny (ACCESS_DENIED) |
投票器拒绝对资源的访问 |
Abstain (ACCESS_ABSTAIN) |
投票器对是否能够访问做了弃权处理(即没有做出决定)。可能在多种原因下发生,如: <!--[if !supportLists]-->l <!--[endif]-->投票器没有确凿的判断信息; <!--[if !supportLists]-->l <!--[endif]-->投票器不能对这种类型的请求做出决策。 |
正如你从访问决策相关类和接口的设计中可以猜到的那样,Spring Security的这部分被精心设计,所以认证和访问控制的使用场景并不仅仅限于web领域。我们将会在:精确的访问控制中关于方法级别的安全时,再次讲解投票器和访问控制管理。
当将他们组合在一起,“对web请求的默认认证检查”的整体流程将如下图所示:
我们可以看到ConfigAttribute能够从配置声明(在DefaultFilterInvocationSecurityMetadataSource类中保存)中传递数据到投票器,投票器并不需要其他的类来理解ConfigAttribute的内容。这种分离能够为新类型的安全声明(例如我们将要看到的方法安全声明)使用相同的访问决策模式提供基础。
实际上Spring Security允许通过security命名空间来配置AccessDecisionManager。<http>元素的access-decision-manager-ref属性来指明一个实现了AccessDecisionManager的Spring Bean。Spring Security提供了这个接口的三个实现类,都在o.s.s.access.vote包中:
类名 |
描述 |
AffirmativeBased |
如果有任何一个投票器允许访问,请求将被立刻允许,而不管之前可能有的拒绝决定。 |
ConsensusBased |
多数票(允许或拒绝)决定了AccessDecisionManager的结果。平局的投票和空票(全是弃权的)的结果是可配置的。 |
UnanimousBased |
所有的投票器必须全是允许的,否则访问将被拒绝。 |
如果你想修改我们的应用来使用UnanimousBased访问决策管理器,我们需要修改两个地方。首先让我们在<http>元素上添加access-decision-manager-ref属性:
<http auto-config="true" access-decision-manager-ref="unanimousBased" > |
这是一个标准的Spring Bean的引用,所以这需要对应一个bean的id属性。接下来,我们要定义这个bean(在dogstore-base.xml中),并与我们引用的有相同的id:
<bean class="org.springframework.security.access.vote.UnanimousBased" id="unanimousBased"> <property name="decisionVoters"> <list> <ref bean="roleVoter"/> <ref bean="authenticatedVoter"/> </list> </property> </bean> <bean class="org.springframework.security.access.vote.RoleVoter" id="roleVoter"/> <bean class="org.springframework.security.access.vote. AuthenticatedVoter" id="authenticatedVoter"/> |
你可能象知道decisionVoters属性是什么。这个属性在我们不声明AccessDecisionManager时,是自动配置的。默认的AccessDecisionManager要求我们配置投票器的一个列表,它们将会在认证决策时用到。这里列出的两个投票器是security命名空间配置默认提供的。
遗憾的是,Spring Security没有为我们提供太多的投票器,但是实现AccessDecisionVoter接口并在配置中添加我们的实现并不是一件困难的事情。我们将在第六章看一个例子。
我们引用的两个投票器介绍如下:
类名 |
描述 |
例子 |
o.s.s.access. vote.RoleVoter |
检查用户是否拥有声明角色的权限(GrantedAuthority)。access属性定义了GrantedAuthority的一个列表。预期会有ROLE_前缀,但这也是可配置的。 |
access="ROLE_USER,ROLE_ADMIN" |
o.s.s.access. vote.AuthenticatedVoter |
支持特定类型的声明,允许使用通配符: <!--[if !supportLists]-->l <!--[endif]-->IS_AUTHENTICATED_FULLY——允许提供完整的用户名和密码的用户访问; <!--[if !supportLists]-->l <!--[endif]-->IS_AUTHENTICATED_REMEMBERED——如果用户是通过remember me功能认证的则允许访问; <!--[if !supportLists]-->l <!--[endif]-->IS_AUTHENTICATED_ANONYMOUSLY——允许匿名用户访问。 |
access=" IS_AUTHENTICATED_ANONYMOUSLY" |