Spring Security Authorization架构简介
Authorities
在身份认证模块中,我们介绍了在认证完成之后,Authentication信息中会存储Authorities列表信息。Authentication信息经由AuthenticationManager对象获取并生成对应示例,然后将会被AccessDecisionManager读取,并用于授权使用。
授权的使用都需要实现GrantedAuthority接口,该接口只有一个方法:
String getAuthority();
该方法返回的是一个String类型,主要是提供给AuthorizationManager来使用,这样的话,当我们返回String类型的时候,需要GrantedAuthority能够精准的表达权限信息,这样能够被大部分的AuthorizationManager实例所识别。如果GrantedAuthority不能表单为精确的授权信息,则需要考虑使用复杂的授权模式,则getAuthority()方法应该返回null
在Spring Security中,提供了GrantedAuthority接口的实现实例:SimpleGrantedAuthority, 该实现能够使得用户将String类型转换为SimpleGrantedAuthority对象实例,所有的AuthenticationProvider实例都是用SimpleGrantedAuthority对象对Authentication信息进行授权。
在默认情况下,如果spring security是基于角色进行授权时,授权信息
authority都会包含ROLE_前缀,也就是说,假如需要包含角色USER,则通过GrantedAuthority#getAuthority()返回ROLE_USER同时Spring Security也提供了修改角色授权,通过
GrantedAuthorityDefaults进行修改。
@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("MYPREFIX_");
}
调用处理
Spring Security 提供了拦截器用于对需要授权资源进行鉴权。Spring Security中,主要通过AuthorizationManager实例进行鉴权处理,鉴权有两种方式:
- 在方法被调用前执行鉴权操作
- 在方法被调用后,对返回的数据执行鉴权
AuthorizationManager
在新版本的Security中,AuthorizationManager完全替代了AccessDecisionManager和AccessDecisionManager。Spring Security 支持基于请求(request-based), 基于方法(method-based)和基于消息(message-based), 然后通过AuthorizationManager做出最终鉴权决策,该类主要包含两个方法:
AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);
default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
throws AccessDeniedException {
// ...
}
check()方法能够获取到需要授权的所有信息,以助于做出鉴权的决策。通过secureObject对象,可以获取到时间安全对象调用中的所有参数信息。对于鉴权的具体逻辑,则是由自定义实现。返回的AuthorizationDecision有以下几种情况true:表示鉴权通过false:表示鉴权失败null:则表示放弃鉴权
verify()方法则是调用check()方法,并判断是否鉴权失败,如果鉴权失败,则抛出AccessDeniedException异常。
基于代理的 AuthorizationManager 实现
虽然用户可以实现AuthorizationManager来实现鉴权的各个方面,但是, Spring Security还是提供了一些实现来与其他的AuthorizationManager实例合作实现鉴权。
例如:
- 当我们需要基于请求(request)进行鉴权时,可以使用
RequestMatcherDelegatingAuthorizationManager - 当基于方法进行鉴权时,可以使用
AuthorizationManagerBeforeMethodInterceptor和AuthorizationManagerAfterMethodInterceptor
Authorization Manager实现的框架图如下:

通过这种方式,那么AuthorizationManager实现组合都能够作用于鉴权决策。
AuthorityAuthorizationManager
该类是作为比较通用的实现,主要是判断Authentication中是否包含了指定的Authority信息,只有在包含时,则AuthorizationDecision返回的结果为true;其他情况返回都是false.
AuthenticatedAuthorizationManager
该类主要是用来区分匿名用户、完全认证的用户和通过remember-me认证的用户。在某些情况下,可能需要remember-me登录的用户在访问某些功能时,需要二次认证。
AuthorizationManagers
该类是静态工厂类,主要作用在于组合单个AuthorizationManager。
自定义Authorization Managers
就是自定义实现AuthorizationManager接口,并且实现鉴权的逻辑。在自定义的实现中,可能和业务逻辑存在必要的耦合关系等。
适配AccessDecisionManager 和 AccessDecisionVoters
在AuthorizationManager出现之前,主要使用的是AccessDecisionManager和AccessDecisionVoters两个类,在移植一些比较老的项目的时候,则鉴权主要使用的是这两个类型,我们可以通过自定义的方式进行改造和实现。
@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
private final AccessDecisionManager accessDecisionManager;
private final SecurityMetadataSource securityMetadataSource;
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
try {
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
this.accessDecisionManager.decide(authentication.get(), object, attributes);
return new AuthorizationDecision(true);
} catch (AccessDeniedException ex) {
return new AuthorizationDecision(false);
}
}
@Override
public void verify(Supplier<Authentication> authentication, Object object) {
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
this.accessDecisionManager.decide(authentication.get(), object, attributes);
}
}
或者只是使用AccessDecisionVoters进行鉴权,则代码如下:
@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
private final AccessDecisionVoter accessDecisionVoter;
private final SecurityMetadataSource securityMetadataSource;
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
switch (decision) {
case ACCESS_GRANTED:
return new AuthorizationDecision(true);
case ACCESS_DENIED:
return new AuthorizationDecision(false);
}
return null;
}
}
以上的两个累心,都需要写入到SecurityFilterChain中。
角色层级
角色层级指代的是,当授权用户具有更高层次的角色时,能够自动的依赖较低层级的角色权限。例如,当前登录用户角色ADMIN,也能够自动拥有USER的权限,这样的话,就可以省去了很多角色配置以及繁杂的配置关系。在Spring Security中也提供了这样的一个实现,具体代码如下:
@Bean
static RoleHierarchy roleHierarchy() {
var hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
}
// and, if using method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
return expressionHandler;
}
在以上的代码中,
>就可以理解为包含关系
以上代码中,包含了四种权限角色:ROLE_ADMIN, ROLE_STAFF, ROLE_USER, ROLE_GUEST,通过以上的表达,当登录角色为ROLE_ADMIN时,就能够自动拥有其他三种角色的权限。
旧版授权组件
spring security包含了很多历史的就组建,处于保留历史信息的目录,这些组件没有做移出操作。
AccessDecisionManager
AccessDecisionManager是被AbstractSecurityInterceptor类所调用,并最终做决定的地方。该类主要包含了三个方法:
void decide(Authentication authentication, Object secureObject,
Collection<ConfigAttribute> attrs) throws AccessDeniedException;
boolean supports(ConfigAttribute attribute);
boolean supports(Class clazz);
decide():该方法能够获取到所有的做最终决策的所有信息。secureObject则是最终需要做决策的安全对象,在运行时得以确认。supports(ConfigAttribute):方法被AbstractSecurityInterceptor类型所调用,用于判断AccessDecisionManager的实例是否支持处理ConfigAttribute对象supports(Class):该方法主要是被security 拦截器的实现所调用,以确保AccessDecisionManager实现类能够处理secureObject具体类型实例。
基于投票的AccessDecisionManager实现
用户能够实现自定义的AccessDecisionManager实例,用于控制鉴权的方方面面,Spring security 包含了基于投票的多个AccessDecisionManager的实现。具体的实现实例如下:

通过上面可以看出,AccessDecisionManager通过AccessDecisionVoter实现实例获取投票结果,并以此判断是否抛出AccessDeniedException信息。
AccessDecisionVoter一共包含了三个方法,如下:
int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs); boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
vote()方法需要返回一个int类型数据,这个类型的数据定义在AccessDecisionVoter中,其中的值意义如下:
ACCESS_GRANTED = 1:代表投票通过ACCESS_ABSTAIN = 0:表示弃权ACCESS_DENIED = -1:代表拒绝
在Spring Security中,提供了三种AccessDecisionManager的实现,具体如下:
ConsensusBased:该实现主要依据同意和拒绝的数量来判定是否授权通过- 所有voter都同意,则通过
- 所有voter都弃权,则根据配置判断是否通过,默认为false
- 如果同意数大于拒绝数,则通过
- 如果同意数小于拒绝数,则抛出
AccessDeniedException异常 - 如果同意数等于拒绝数,则需要根据配置
allowIfEqualGrantedDeniedDecisions判断是否通过,默认为true; 否则抛出AccessDeniedException异常
AffirmativeBased:该实现不会关注同意的数量,而是值关注拒绝的数量,因此当有voter拒绝之后,则会抛出AccessDeniedException异常。同时,针对所有voter都弃权的情况,可以通过配置allowIfAllAbstainDecisions进行操作UnanimousBased:与AffirmativeBased刚好相反,该实现不关注拒绝的数量,只关注统一的数量,因此,只要有一个同意,则视为通过。同时,只要有一个拒绝,则会抛出AccessDeniedException异常。对于所有的都弃权的情况,则可以根据allowIfAllAbstainDecisions进行控制。
当然我们也可以通过自定义的方式来实现自定义的逻辑判断。
RoleVoter
该实现方式应该是比较常用的AccessDecisionVoter实现,该类的实现主要比对认证的用户信息中是否包含了指定的ROLE_前缀的授权信息,如果明确的指定了,则返回认证成功。否者就是认证失败。
AuthenticatedVoter
该类主要是用来判断匿名用户,完全认证、和通过remember-me进行认证的用户,在某些网站的实现中,通过remember-me进行认证的用户会收到部分权限的限制,因此在访问某些功能的时候,需要二次认证。
该类如果在进行鉴权的时候,使用了IS_AUTHENTICATED_ANONYMOUSLY配置,则代表了以上三种情况都是支持的,同时也是支持匿名用户,则返回同意的结果。
自定义Voters

跟其他的实现一样的,AfterInvocationManager的实现也是只有一个AfterInvocationProviderManager对象,该实现持有了AfterInvocationProvider列表,并且每个实例都能够修改返回值或者抛出AccessDeniedException异常。并且每个AfterInvocationProvder修改后的结果会传给下一个provider实例。
