Spring Security 鉴权原理
1. 前言
在前面的章节里,我们讨论了 Spring Security 如何实现「认证」,本节开始,我们进入 Spring Security 的「鉴权」部分。
鉴权(Authorization)是指验证用户是否拥有访问系统的权力,它的前提是用户的身份已经得到有效地认证,也是认证的目标。
Spring Security 提供的鉴权能力的最大特点是,无关于开发人员所选用的认证方式(密码认证、OAuth2.0 认证、证书认证等),即使它不是在 Spring Security 认证范围之内(预认证等),也可以简单快捷的基础 Spring Security 鉴权功能。
本节我们主要讨论 Spring Security 关于鉴权部分的实现思路。
2. Spring Security 鉴权框架
2.1 权限
Spring Security 中,在用户身份信息得到确认后,Authentication 中会存储一系列的 GrantedAuthority 对象,这些对象用来判断用户可以使用哪些资源。
GrantedAuthority 对象通过 AuthenticationManager 插入到 Authentication 对象中,并被 AccessDecisionManager 使用,判断其权限。
GrantedAuthority 是一个接口,其仅包含一个方法:
String getAuthority();
getAuthority()
方法返回一个字符串值,该值作为权限的描述,当权限较为复杂,不是字符串可以描述的情况时,该方法需要返回 null
。这种复杂的权限可能是「多种操作对应多个不同用户」等类似情况,用一个字符串很难表达完整,此时 AccessDecisionManager 会根据 getAuthority()
返回值情况判断是否要进行特殊处理。
在 Spring Security 中,GrantedAuthority 有一个基础实现类 SimpleGrantedAuthority
可以满足一般的业务需求。
2.2 前置鉴权
Spring Security 的权限判断可以发生在方法被调用前,或者 WEB 请求之前。在请求的开始,由 AccessDecisionManager
对象将判断其是否允许继续执行。
AccessDecisionManager
对象由 AbstractSecurityInterceptor
发起调用,其职责是给出资源是否能被访问的最终结果,AccessDecisionManager
包含三个主要方法:
- 判断配置属性是否可被访问;
boolean supports(ConfigAttribute attribute);
- 判断安全对象的类型是否支持被访问;
boolean supports(Class clazz);
- 通过认证信息、安全对象、权限信息综合判断安全对象是否允许被访问。
void decide(Authentication authentication, Object secureObject,
Collection<ConfigAttribute> attrs) throws AccessDeniedException;
Spring Security 的鉴权策略可以由用户自己实现,在 Spring Security 内部也实现了一套鉴权策略,称为「基于投票的访问决策管理」。
在这种策略下,AccessDecisionManager
控制着一系列的 AccessDecisionVoter
实例,判断权限是否满足,如果不满足抛出 AccessDeniedException
异常。
AccessDecisionVoter
也包含三个方法:
- 判断配置属性是否支持;
boolean supports(ConfigAttribute attribute);
- 判断类型是否支持;
boolean supports(Class clazz);
- 根据认证信息对安全资源进行投票。
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);
投票鉴权分为三类:
- 基于角色的投票:RoleVoter;
- 基于认证信息的投票:AuthenticatedVoter,主要区分认证用户、匿名用户等;
- 自定义投票策略。
2.3 后置鉴权
有时候,权限的判断是在资源被调动之后才处理的,此过程就是后置鉴权。
在 Spring Security 中,后置鉴权通过 AfterInvocationManager
进行管理。
后置鉴权在资源被访问后,根据权限的判定来修改返回的内容,或者返回 AccessDeniedException
。
2.4 多级角色
有时候,一个角色可能涵盖了多种其他角色。例如:admin 可能包含 user 的权限,为实现这一功能,我们可能会为 admin 用户增加 user 角色。
要实现这种配置,我们需要用到 RoleVoter 的扩展实现类,RoleHierarchyVoter
。其配置方式如下:
<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_STAFF
ROLE_STAFF > ROLE_USER
ROLE_USER > ROLE_GUEST
</value>
</property>
</bean>
这个配置中出现了四种角色,ADMIN、STAFF、USER、GUEST,而这四种又有明显的包含于被包含关系,如此便可以使角色投票机制根据角色的层级进行决策。
3. 小结
本节我们讨论了 Spring Security 中鉴权的基本设计思路,主要内容如下:
- Spring Security 包含确认身份和确认身份的可执行操作两部分,后者即为鉴权;
- Spring Security 的权限默认是以字符串形式存储的权限信息,比如角色名称、功能名称等;
- Spring Security 内置了以「投票」为判定方法的鉴权策略。
下节我们讨论如何为 Servlet 应用提供鉴权能力。