转载

Spring Security(15)——权限鉴定结构

权限鉴定结构

目录

1.1       权限

1.2       调用前的处理

1.2.1      AccessDecisionManager

1.2.2      基于投票的 AccessDecisionManager 实现

1.3       调用后的处理

1.4       角色的继承

1.1      权限

所有的 Authentication 实现类都保存了一个 GrantedAuthority 列表,其表示用户所具有的权限。 GrantedAuthority 是通过 AuthenticationManager 设置到 Authentication 对象中的,然后 AccessDecisionManager 将从 Authentication 中获取用户所具有的 GrantedAuthority 来鉴定用户是否具有访问对应资源的权限。

GrantedAuthority 是一个接口,其中只定义了一个 getAuthority() 方法,其返回值为 String 类型。该方法允许 AccessDecisionManager 获取一个能够精确代表该权限的字符串。通过返回一个字符串,一个 GrantedAuthority 能够很轻易的被大部分 AccessDecisionManager 读取。如果一个 GrantedAuthority 不能够精确的使用一个 String 来表示,那么其对应的 getAuthority() 方法调用应当返回一个 null ,这表示 AccessDecisionManager 必须对该 GrantedAuthority 的实现有特定的支持,从而可以获取该 GrantedAuthority 所代表的权限信息。

Spring Security 内置了一个 GrantedAuthority 的实现, SimpleGrantedAuthority 。它直接接收一个表示权限信息的字符串,然后 getAuthority() 方法直接返回该字符串。 Spring Security 内置的所有 AuthenticationProvider 都是使用它来封装 Authentication 对象的。

1.2      调用前的处理

Spring Security 是通过拦截器来控制受保护对象的访问的,如方法调用和 Web 请求。在正式访问受保护对象之前, Spring Security 将使用 AccessDecisionManager 来鉴定当前用户是否有访问对应受保护对象的权限。

1.2.1AccessDecisionManager

AccessDecisionManager 是由 AbstractSecurityInterceptor 调用的,它负责鉴定用户是否有访问对应资源(方法或 URL )的权限。 AccessDecisionManager 是一个接口,其中只定义了三个方法,其定义如下。

public  interface AccessDecisionManager {

/**

* 通过传递的参数来决定用户是否有访问对应受保护对象的权限

*

* @param authentication 当前正在请求受包含对象的 Authentication

* @param object 受保护对象,其可以是一个 MethodInvocation JoinPoint FilterInvocation

* @param configAttributes 与正在请求的受保护对象相关联的配置属性

*

*/

void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)

throws AccessDeniedException, InsufficientAuthenticationException;

/**

* 表示当前 AccessDecisionManager 是否支持对应的 ConfigAttribute

*/

boolean supports(ConfigAttribute attribute);

/**

* 表示当前 AccessDecisionManager 是否支持对应的受保护对象类型

*/

boolean supports(Class<?> clazz);

}

decide() 方法用于决定 authentication 是否符合受保护对象要求的 configAttributes supports(ConfigAttribute attribute) 方法是用来判断 AccessDecisionManager 是否能够处理对应的 ConfigAttribute 的。 supports(Class<?> clazz) 方法用于判断配置的 AccessDecisionManager 是否支持对应的受保护对象类型。

1.2.2基于投票的AccessDecisionManager实现

Spring Security 已经内置了几个基于投票的 AccessDecisionManager ,当然如果需要你也可以实现自己的 AccessDecisionManager 。以下是 Spring Security 官方文档提供的一个图,其展示了与基于投票的 AccessDecisionManager 实现相关的类。

Spring Security(15)——权限鉴定结构

使用这种方式,一系列的 AccessDecisionVoter 将会被 AccessDecisionManager 用来对 Authentication 是否有权访问受保护对象进行投票,然后再根据投票结果来决定是否要抛出 AccessDeniedException AccessDecisionVoter 是一个接口,其中定义有三个方法,具体结构如下所示。

public  interface AccessDecisionVoter<S> {

int ACCESS_GRANTED = 1;

int ACCESS_ABSTAIN = 0;

int ACCESS_DENIED = -1;

boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);

int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);

}

vote() 方法的返回结果会是 AccessDecisionVoter 中定义的三个常量之一。 ACCESS_GRANTED 表示同意, ACCESS_DENIED 表示返回, ACCESS_ABSTAIN 表示弃权。如果一个 AccessDecisionVoter 不能判定当前 Authentication 是否拥有访问对应受保护对象的权限,则其 vote() 方法的返回值应当为弃权 ACCESS_ABSTAIN

Spring Security 内置了三个基于投票的 AccessDecisionManager 实现类,它们分别是 AffirmativeBased ConsensusBased UnanimousBased

AffirmativeBased 的逻辑是这样的:

1 )只要有 AccessDecisionVoter 的投票为 ACCESS_GRANTED 则同意用户进行访问;

2 )如果全部弃权也表示通过;

3 )如果没有一个人投赞成票,但是有人投反对票,则将抛出 AccessDeniedException

ConsensusBased 的逻辑是这样的:

1 )如果赞成票多于反对票则表示通过。

2 )反过来,如果反对票多于赞成票则将抛出 AccessDeniedException

3 )如果赞成票与反对票相同且不等于 0 ,并且属性 allowIfEqualGrantedDeniedDecisions 的值为 true ,则表示通过,否则将抛出异常 AccessDeniedException 。参数 allowIfEqualGrantedDeniedDecisions 的值默认为 true

4 )如果所有的 AccessDecisionVoter 都弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定,如果该值为 true 则表示通过,否则将抛出异常 AccessDeniedException 。参数 allowIfAllAbstainDecisions 的值默认为 false

UnanimousBased 的逻辑与另外两种实现有点不一样,另外两种会一次性把受保护对象的配置属性全部传递给 AccessDecisionVoter 进行投票,而 UnanimousBased 会一次只传递一个 ConfigAttribute AccessDecisionVoter 进行投票。这也就意味着如果我们的 AccessDecisionVoter 的逻辑是只要传递进来的 ConfigAttribute 中有一个能够匹配则投赞成票,但是放到 UnanimousBased 中其投票结果就不一定是赞成了。 UnanimousBased 的逻辑具体来说是这样的:

1 )如果受保护对象配置的某一个 ConfigAttribute 被任意的 AccessDecisionVoter 反对了,则将抛出 AccessDeniedException

2 )如果没有反对票,但是有赞成票,则表示通过。

3 )如果全部弃权了,则将视参数 allowIfAllAbstainDecisions 的值而定, true 则通过, false 则抛出 AccessDeniedException

1.2.2.1 RoleVoter

RoleVoter Spring Security 内置的一个 AccessDecisionVoter ,其会将 ConfigAttribute 简单的看作是一个角色名称,在投票的时如果拥有该角色即投赞成票。如果 ConfigAttribute 是以“ ROLE_ ”开头的,则将使用 RoleVoter 进行投票。当用户拥有的权限中有一个或多个能匹配受保护对象配置的以“ ROLE_ ”开头的 ConfigAttribute 时其将投赞成票;如果用户拥有的权限中没有一个能匹配受保护对象配置的以“ ROLE_ ”开头的 ConfigAttribute ,则 RoleVoter 将投反对票;如果受保护对象配置的 ConfigAttribute 中没有以“ ROLE_ ”开头的,则 RoleVoter 将弃权。

1.2.2.2 AuthenticatedVoter

AuthenticatedVoter 也是 Spring Security 内置的一个 AccessDecisionVoter 实现。其主要用来区分匿名用户、通过 Remember-Me 认证的用户和完全认证的用户。完全认证的用户是指由系统提供的登录入口进行成功登录认证的用户。

AuthenticatedVoter 可以处理的 ConfigAttribute IS_AUTHENTICATED_FULLY IS_AUTHENTICATED_REMEMBERED IS_AUTHENTICATED_ANONYMOUSLY 。如果 ConfigAttribute 不在这三者范围之内,则 AuthenticatedVoter 将弃权。否则将视 ConfigAttribute 而定,如果 ConfigAttribute IS_AUTHENTICATED_ANONYMOUSLY ,则不管用户是匿名的还是已经认证的都将投赞成票;如果是 IS_AUTHENTICATED_REMEMBERED 则仅当用户是由 Remember-Me 自动登录,或者是通过登录入口进行登录认证时才会投赞成票,否则将投反对票;而当 ConfigAttribute IS_AUTHENTICATED_FULLY 时仅当用户是通过登录入口进行登录的才会投赞成票,否则将投反对票。

AuthenticatedVoter 是通过 AuthenticationTrustResolver isAnonymous() 方法和 isRememberMe() 方法来判断 SecurityContextHolder 持有的 Authentication 是否为 AnonymousAuthenticationToken RememberMeAuthenticationToken 的,即是否为 IS_AUTHENTICATED_ANONYMOUSLY IS_AUTHENTICATED_REMEMBERED

1.2.2.3 自定义 Voter

当然,用户也可以通过实现 AccessDecisionVoter 来实现自己的投票逻辑。

1.3      调用后的处理

AccessDecisionManager 是用来在访问受保护的对象之前判断用户是否拥有访问该对象的权限。有的时候我们可能会希望在请求执行完成后对返回值做一些修改,当然,你可以简单的通过 AOP 来实现这一功能。 Spring Security 为我们提供了一个 AfterInvocationManager 接口,它允许我们在受保护对象访问完成后对返回值进行修改或者进行权限鉴定,看是否需要抛出 AccessDeniedException ,其将由 AbstractSecurityInterceptor 的子类进行调用。 AfterInvocationManager 接口的定义如下。

public  interface AfterInvocationManager {

Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,

Object returnedObject) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);

}

以下是 Spring Security 官方文档提供的一个 AfterInvocationManager 构造实现的图。

Spring Security(15)——权限鉴定结构

类似于 AuthenticationManager AfterInvocationManager 拥有一个默认的实现类 AfterInvocationProviderManager ,其中拥有一个由 AfterInvocationProvider 组成的集合, AfterInvocationProvider AfterInvocationManager 具有相同的方法定义,在调用 AfterInvocationProviderManager 中的方法时实际上就是依次调用其中包含的 AfterInvocationProvider 对应的方法。

需要注意的是 AfterInvocationManager 需要在受保护对象成功被访问后才能执行。

1.4      角色的继承

对于角色继承这种需求也是经常有的,比如要求 ROLE_ADMIN 将拥有所有的 ROLE_USER 所具有的权限。当然我们可以给拥有 ROLE_ADMIN 角色的用户同时授予 ROLE_USER 角色来达到这一效果或者修改需要 ROLE_USER 进行访问的资源使用 ROLE_ADMIN 也可以访问。 Spring Security 为我们提供了一种更为简便的办法,那就是角色的继承,它允许我们的 ROLE_ADMIN 直接继承 ROLE_USER ,这样所有 ROLE_USER 可以访问的资源 ROLE_ADMIN 也可以访问。定义角色的继承我们需要在 ApplicationContext 中定义一个 RoleHierarchy ,然后再把它赋予给一个 RoleHierarchyVoter ,之后再把该 RoleHierarchyVoter 加入到我们基于 Voter AccessDecisionManager 中,并指定当前使用的 AccessDecisionManager 为我们自己定义的那个。以下是一个定义角色继承的完整示例。

< beans xmlns = "http://www.springframework.org/schema/beans"

xmlns:security = "http://www.springframework.org/schema/security"

xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation = "http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.1.xsd

http://www.springframework.org/schema/security

          http://www.springframework.org/schema/security/spring-security-3.1.xsd" >

<!-- 通过 access-decision-manager- ref 指定将要使用的 AccessDecisionManager -->

< security:http access-decision-manager-ref = "accessDecisionManager" >

< security:form-login />

< security:intercept-url pattern = "/admin.jsp" access = "ROLE_ADMIN" />

< security:intercept-url pattern = "/**" access = "ROLE_USER" />

</ security:http >

< security:authentication-manager alias = "authenticationManager" >

< security:authentication-provider

user-service-ref = "userDetailsService" />

</ security:authentication-manager >

< bean id = "userDetailsService"

class = "org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl" >

< property name = "dataSource" ref = "dataSource" />

</ bean >

<!-- 自己定义 AccessDecisionManager 对应的 bean -->

< bean id = "accessDecisionManager" class = "org.springframework.security.access.vote.AffirmativeBased" >

< property name = "decisionVoters" >

< list >

< ref local = "roleVoter" />

</ list >

</ property >

</ bean >

< bean id = "roleVoter"

class = "org.springframework.security.access.vote.RoleHierarchyVoter" >

< constructor-arg ref = "roleHierarchy" />

</ bean >

< bean id = "roleHierarchy"

class = "org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" >

< property name = "hierarchy" > <!-- 角色继承关系 -->

< value >

ROLE_ADMIN > ROLE_USER

</ value >

</ property >

</ bean >

</ beans >

在上述配置中我们就定义好了 ROLE_ADMIN 是继承自 ROLE_USER 的,这样 ROLE_ADMIN 将能够访问所有 ROLE_USER 可以访问的资源。通过 RoleHierarchyImpl hierarchy 属性我们可以定义多个角色之间的继承关系,如:

< bean id = "roleHierarchy"

class = "org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl" >

< property name = "hierarchy" > <!-- 角色继承关系 -->

< value >

ROLE_ADMIN > ROLE_USER

ROLE_A > ROLE_B

ROLE_B > ROLE_C

ROLE_C > ROLE_D

</ value >

</ property >

</ bean >

在上述配置我们同时定义了 ROLE_ADMIN 继承了 ROLE_USER ROLE_A 继承了 ROLE_B ROLE_B 又继承了 ROLE_C ROLE_C 又继承了 ROLE_D ,这样 ROLE_A 将能访问 ROLE_B ROLE_C ROLE_D 所能访问的所有资源。

(注:本文是基于 Spring Security3.1.6 所写)

正文到此结束
Loading...