spring security 鉴权

无论是否使用spring-security的认证, 都可以使用spring-security的鉴权功能。

鉴权架构

权限 Authorities

认证的时候AuthenticationManager会把授予主体的权限(GrantedAuthoritys)存进Authentication里。后面用户访问应用资源的时候, AccessDecisionManager 会根据GrantedAuthority做鉴权, 判断用户是否有权限访问此资源。

GrantedAuthority 是一个接口, 只有一个方法:

1
String getAuthority();

AccessDecisionManager调用此方法等到一个String来表示权限。如果GrantedAuthority实现类不能转为String 应该返回null

GrantedAuthority有一个具体实现SimpleGrantedAuthority,可以把指定字符串转为 GrantedAuthority。spring security 认证体系中的所有AuthenticationProvider 都是用此类来填充Authentication对象。

预调用处理

spring security 提供有拦截器来控制对安全对象(如:方法、web请求等)访问, 通过AccessDecisionManager 来决定是否可以访问。

The AccessDecisionManager

AccessDecisionManager 负责做访问控制的决策,由AbstractSecurityInterceptor调用。AccessDecisionManager接口包含以下三个方法:

1
2
3
4
5
6
void decide(Authentication authentication, Object secureObject,
Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

decide 方法的参数中包含了和授权决定相关的所有信息。特别是secureObject包含了安全对象被调用的时候的所有参数(比如方法调用参数, web请求的参数), 如果拒绝访问的话会抛出AccessDeniedException异常。

supports(ConfigAttribute) 方法由 AbstractSecurityInterceptor 在鉴权开始时调用,以确定 AccessDecisionManager 是否可以处理传入的 ConfigAttribute

supports(Class) 表示此AccessDecisionManager 是否能处理指定的secureObject 类。

基于投票的 AccessDecisionManager 实现

用户实现自己的AccessDecisionManager 来鉴权的各个方面。

Spring security 自身包含了几个基于投票的AccessDecisionManager实现。

投票鉴权相关的类:

img

这种方式下, 一次鉴权过程中, 一系列的AccessDecisionVoter(选票)会被轮询。AccessDecisionManager 然后根据投票评估决定是否抛出 AccessDeniedException异常。

AccessDecisionVoter有以下三个方法:

1
2
3
4
5
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

vote返回一个int,可能的值定义在AccessDecisionVoter静态常量字段里:ACCESS_ABSTAINACCESS_DENIED and,ACCESS_GRANTED,分别表示弃权拒绝同意

AccessDecisionManager 有三种具体的投票决定实现:

  1. ConsensusBased:共识方案, 少数服从多数。 有属性可以控制票数一样或者全部弃权的情况怎么处理
  2. AffirmativeBased: 可决票方案。 只要有一个赞成票就通过。有属性可以控制全部弃权的情况怎么处理
  3. UnanimousBased:一致性方案。 一票反对就拒绝(并非要求全票通过, 可以有弃权票)。有属性可以控制全部弃权的情况怎么处理
RoleVoter 角色投票人

RoleVoter 是spring security 提供的一种AccessDecisionVoter, 是最常用的一咱AccessDecisionVoter, 顾名思义就是以角色作为判断标准来投票。它把参数Collection<ConfigAttribute> attrs的每个ConfigAttribute都当作 角色名 来处理,如果用户有这个角色,就授权。

如果有任意ConfigAttribute是以ROLE_开头,RoleVoter就会投票。如果主体有GrantedAuthority 和一个或者多个ROLE_开头的ConfigAttribute相等,就投赞成票。 如果没有就投反对票。如果没有ROLE_开头的ConfigAttribute就弃权。

AuthenticatedVoter 认证投票人

根据身份认证的情况来投票,可以区分匿名(anonymous)、完全认证(fully-authenticated)、记住我(remember-me)方式认证。

使用IS_AUTHENTICATED_ANONYMOUSLY来授权访问时,AuthenticatedVoter 会参与投票。

Custom Voters自定义投票人

可实现自己的AccessDecisionVoter 来定制访问控制逻辑。示例blog

After Invocation Handling(ACL) 调用后处理

AccessDecisionManager 在进行安全对象调用之前由 AbstractSecurityInterceptor 调用, 负责调用前处理。但是有些应用需要的安全对象调用后,修改其返回内容。虽然可以通过AOP方式实现, 但Spring Security 提供了一个方便的钩子,提供调用后处理的能力, 它有几个具体实现:

Figure 12. After Invocation Implementation

AfterInvocationManager 有一个简单的具体实现AfterInvocationProviderManager, 它通过轮询AfterInvocationProvider列表来实现具体功能。每个AfterInvocationProvider都可以修以返回内容, 或者抛出AccessDeniedException异常。可以多个AfterInvocationProvider都修改返回内容, 上一个修改的内容会 作为参数传递给下一个。

Hierarchical Roles 角色分层

支持角色的继承关系,RoleHierarchyVoter

FilterSecurityInterceptorHttpServletRequest鉴权

本节讨论鉴权的Servlet下如何实现的。

FilterSecurityInterceptorHttpServletRequests 提供鉴权。它作为Security Filters之一插入到 FilterChainProxy 中。

Figure 13. Authorize HttpServletRequest

  1. 首先,FilterSecurityInterceptor SecurityContextHolder获取一个Authentication表示当前用户(不一定登录了)

  2. 然后,FilterSecurityInterceptor根据传入的HttpServletRequest, HttpServletResponse, and FilterChain构建一个FilterInvocation

  3. 接着,把FilterInvocation传给SecurityMetadataSource以获取ConfigAttribute列表。

  4. 最后,把

    1
    Authentication

    1
    FilterInvocation

    1
    ConfigAttribute

    传给

    1
    AccessDecisionManager.decide()

    方法。

    • 如果鉴权拒绝,抛出AccessDeniedException异常, ExceptionTranslationFilter会处理这个异常。
    • 如果有权限,FilterSecurityInterceptor 调用过滤链中的下一个过滤器。

默认情况下, Spring Security要求所有的请求都通过身份认证,显示配置类似:

1
2
3
4
5
6
7
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize
.anyRequest().authenticated()
);
}

我们可以通过按优先顺序来添加更多的的规则:

1
2
3
4
5
6
7
8
9
10
protected void configure(HttpSecurity http) throws Exception {
http
// ...
.authorizeRequests(authorize -> authorize // 1
.mvcMatchers("/resources/**", "/signup", "/about").permitAll() // 2
.mvcMatchers("/admin/**").hasRole("ADMIN") // 3
.mvcMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // 4
.anyRequest().denyAll() // 5
);
}
  1. 这里配置了多个鉴权规则, 按定义的顺序处理, 这里配置的规则是就前面提到的ConfigAttribute
  2. 可匹配多个URL模式。permitAll方法授予所有人权限。以”/resources” 开头的,以及等于”/signup”, “/about” 的URL所有人都可以访问。
  3. 使用hasRole方法。“/admin” 开头的URL必须要有“ROLE_ADMIN” 角色才能访问。如果用hasRole方法,参数不需要ROLE_前缀
  4. 使用鉴权表达式。 鉴权表大式里hasRole也不需要ROLE_前缀。
  5. anyRequest表示全部请求。denyAll拒绝所有人访问。

Expression-Based Access Control 鉴权表达式

除了上面提到的配置方式外,Spring Security 3 开始还可以使用Spring EL表达式作为一种鉴权机制。 鉴权表达式也是构建这相同架构之上的,但是可以用一个表达式处理更复杂的逻辑判断。

概览

Spring Security 以Spring EL为基础,如果有兴趣更深入地理解该主题,应该看看它是如何工作的。 表达式使用一个 根对象 作为计算上下文。Spring Security 为WEB请求和方法,分别提供了特定的类作为根对象(如:当前主体),表达式从其中取值进行计算。

Common Built-In Expressions 常用的内置表达式
描述 表达式
hasRole(String role) 有此角色返回true。默认情况下如果参数指定的角色名没有以ROLE_开头,表达式会加上这个前缀,比如:hasRole("ADMIN")实际会判断ROLE_ADMIN角色。可通过设置DefaultWebSecurityExpressionHandler.defaultRolePrefix修改默认前缀。
hasAnyRole(String… roles) 是否包含任一指定角色。前缀规则同上。
hasAuthority(String authority) 是否有指定权限
hasAnyAuthority(String… authorities) 是否有任一指定的权限
principal 表达式里可以直接访问当前主体对象。
authentication 表达式可直接访问当前Authentication对象
permitAll 直接返回true
denyAll 直接返回false
isAnonymous() 是否是匿名用户
isRememberMe() 是否是记住我登录
isAuthenticated() 是否通过身份认证(非匿名用户),
isFullyAuthenticated() 是否是完全认证(非匿名、非记住我)
hasPermission(Object target, Object permission) 领域对象安全验证, 是否有访问target对象的某种权限。比如hasPermission(domainObject, 'read')
hasPermission(Object targetId, String targetType, Object permission) 领域对象安全验证,同上,不过对象是通过id和类型来指定的。如:hasPermission(1, 'com.example.domain.Message', 'read')

Web Security Expressions Web安全表达式

//todo

Method Security Expressions 方法安全表达式

方法安全性比简单的允许或拒绝规则要复杂一些。 Spring Security 3.0 引入了一些新的注解,以便全面支持表达式的使用。

@Pre 和 @Post 注解

有四个注解支持表达式属性,以允许调用前和调用后的授权检查,还支持过滤提交的集合参数或返回值。分别是: @PreAuthorize, @PreFilter, @PostAuthorize@PostFilter

总得来说就是在方法上使用注解,来做鉴权。细节以后再看。