目录
1、Authorities 授权(AuthorizationFilter 过滤器)
2、AuthorizationManager 授权管理器
3、角色的层次化(Roles)
通过 Authentication.getAuthorities() 可获取已通过验证的用户授权信息(GrantedAuthority),GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,然后在做出授权决策时由 AuthorizationManager 实例读取。
AuthenticationManager 的常用实现为 ProviderManager,ProviderManager.authenticate 在进行身份验证完成后,会填充 Authentication 对象,其中就包括对象的授权信息(GrantedAuthority)。
//具体授权的Provider列表
private List providers;
//授权方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//省略...
//1-定义身份验证对象
Authentication result = null;
Authentication parentResult = null;
int currentPosition = 0;
int size = this.providers.size();
Iterator var9 = this.getProviders().iterator();
while(var9.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var9.next();
if (provider.supports(toTest)) {
//省略...
try {
//2-通过用户验证后,在这里返回填充好的Authentication对象
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (InternalAuthenticationServiceException | AccountStatusException var14) {
this.prepareException(var14, authentication);
throw var14;
} catch (AuthenticationException var15) {
lastException = var15;
}
}
}
//省略...
if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
}
if (parentResult == null) {
this.eventPublisher.publishAuthenticationSuccess(result);
}
//3-在这里将封装的对象返回
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
}
if (parentException == null) {
this.prepareException((AuthenticationException)lastException, authentication);
}
throw lastException;
}
}
AuthenticationProvider 的实现有很多,这里使用常用的 AbstractUserDetailsAuthenticationProvider 来看一下用户权限的填充步骤。
//AbstractUserDetailsAuthenticationProvider.authenticate方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//省略...
//1-获取用户名称
String username = this.determineUsername(authentication);
//2-从缓存中获取用户信息(UserDetails)
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//3-缓存中无用户信息,再去查找其他UserDetails的实现
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
//错误处理,省略...
}
//省略..
//4-最后这个返回,就是返回封装好的Authentication对象
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
//封装Authentication对象
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
//在这里设置了用户的权限->user.getAuthorities() -> GrantedAuthority
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
this.logger.debug("Authenticated user");
return result;
}
用户权限填充完了,什么时候取用呢?权限信息由授权过滤器 AuthorizationFilter 在进行授权的时候取用。
//授权过滤器
public class AuthorizationFilter extends OncePerRequestFilter {
//授权管理器
private final AuthorizationManager authorizationManager;
public AuthorizationFilter(AuthorizationManager authorizationManager) {
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.authorizationManager = authorizationManager;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// verify方法进行授权验证,this::getAuthentication 获取权限信息
this.authorizationManager.verify(this::getAuthentication, request);
filterChain.doFilter(request, response);
}
private Authentication getAuthentication() {
//从SecurityContextHolder中获取权限信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException("An Authentication object was not found in the SecurityContext");
} else {
return authentication;
}
}
}
AuthorizationManager,用来确定 Authentication 是否有权访问特定对象。
在 Spring Security 中 AuthorizationManager 取代了 AccessDecisionManager 和AccessDecisionVoter。
Spring 官方鼓励自定义 AccessDecisionManager 或 AccessDecisionVoter 的应用程序改为使用 AuthorizationManager。
AuthorizationManager 由 AuthorizationFilter 调用,负责做出最终的访问控制决策。AuthorizationManager 接口包含两个方法://函数式接口
@FunctionalInterface
public interface AuthorizationManager {
//确定是否为特定Authentication或对象授予访问权限。
default void verify(Supplier authentication, T object) {
AuthorizationDecision decision = this.check(authentication, object);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
}
//确定是否为特定Authentication或对象授予访问权限。
@Nullable
AuthorizationDecision check(Supplier var1, T var2);
}
用户可以通过实现 AuthorizationManager 接口来自定义授权控制,同时,Spring Security 附带了一个委托 AuthorizationManager,它可以与各个 AuthorizationManager 进行协作。
RequestMatcherDelegatingAuthorizationManager 将选择最合适的 AuthorizationManager 匹配请求。对于方法的安全控制,可以使用 AuthorizationManagerBeforeMethodInterceptor 和 AuthorizationManagerAfterMethodInterceptor 进行实现。//注意,这两个拦截器在5.6后的版本中才有
//请求匹配器和授权管理器的集合
private final Map> mappings;
//RequestMatcherDelegatingAuthorizationManager.check方法
public AuthorizationDecision check(Supplier authentication, HttpServletRequest request) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s", request));
}
//1-获取请求匹配器和授权管理器集合的迭代器
Iterator var3 = this.mappings.entrySet().iterator();
Entry mapping;
MatchResult matchResult;
do {
if (!var3.hasNext()) {
this.logger.trace("Abstaining since did not find matching RequestMatcher");
return null;
}
//2-获取一个Entry
mapping = (Entry)var3.next();
//3-获取一个RequestMatcher
RequestMatcher matcher = (RequestMatcher)mapping.getKey();
//4-进行匹配
matchResult = matcher.matcher(request);
} while(!matchResult.isMatch());
//5-如果匹配成功,会选择一个对应的授权器:AuthorizationManager
AuthorizationManager manager = (AuthorizationManager)mapping.getValue();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
}
//6-选择特定的授权管理器进行授权操作
return manager.check(authentication, new RequestAuthorizationContext(request, matchResult.getVariables()));
}
AuthorizationManagerBeforeMethodInterceptor: 是一个方法拦截器,它使用 AuthorizationManager 来确定一个 Authentication 是否可以调用给定的 MethodInvocation。//该拦截器在方法调用前进行拦截
//授权管理器
private final AuthorizationManager authorizationManager;
//AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization方法
//在该拦截器中尝试使用授权管理器进行授权
private void attemptAuthorization(MethodInvocation mi) {
this.logger.debug(LogMessage.of(() -> {
return "Authorizing method invocation " + mi;
}));
//1-执行授权操作
AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);
//2-发布授权事件(Authentication对象,调用方法,授权结果)
this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);
if (decision != null && !decision.isGranted()) {
//3-授权失败,记录日志,抛出异常
this.logger.debug(LogMessage.of(() -> {
return "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision;
}));
throw new AccessDeniedException("Access Denied");
} else {
//4-授权成功,记录日志,后续执行指定方法
this.logger.debug(LogMessage.of(() -> {
return "Authorized method invocation " + mi;
}));
}
}
//AuthorizationManagerBeforeMethodInterceptor.invoke方法
public Object invoke(MethodInvocation mi) throws Throwable {
//1-调用拦截器进行授权
this.attemptAuthorization(mi);
//2-授权成功后执行指定方法(此时授权没有抛出异常)
return mi.proceed();
}
对应的授权管理器,Spring Security 提供了指定的授权器 PreAuthorizeAuthorizationManager,在该授权器中执行授权相关操作。
AuthorizationManagerAfterMethodInterceptor:也是一个MethodInterceptor,它可以使用 AuthorizationManager 来确定一个 Authentication 是否可以访问一个 MethodInvocation 的结果。//这个拦截器是在方法调用结束后进行拦截
授权管理器(AuthorizationManager)的实现类图示:
应用程序中的尝尝指定一个特定的角色会自动“包含”其他的角色。例如,在具有“Admin”和“User”角色概念的应用程序中,希望管理员(“Admin”)能够执行普通用户(“User”)的所有操作。要实现这一需求,就要确保所有管理员角色也被分配了“User”角色。
角色层次结构允许配置一个角色(或权限)时可以包含其他角色。Spring Security 的 RoleVoter 的扩展版本 RoleHierarchyVoter 配置了一个 RoleHierarchy,它从中获得分配给用户的所有“可访问权限”。//角色选择器
典型的配置可能是这样的:
@Bean
AccessDecisionVoter hierarchyVoter() {
RoleHierarchy hierarchy = new RoleHierarchyImpl();
hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
"ROLE_STAFF > ROLE_USER\n" +
"ROLE_USER > ROLE_GUEST");
return new RoleHierarchyVoter(hierarchy);
}
在上边代码中定义了四个层次结构的角色:
ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST
使用 ROLE_ADMIN 角色进行身份验证的用户,会同时拥有其他角色的权限,其中 ">" 可以被看成”包括“的意思。
注意:RoleHierarchy bean 配置还没有移植到 @EnableMethodSecurity。因此,本例使用的是 AccessDecisionVoter。如果需要 RoleHierarchy 来支持方法的安全性,请继续使用@EnableGlobalMethodSecurity,直到这个bug修复完成。//这个配置是有bug的,后续应该可以正常使用
顺便提一下,Spring Security 也提供了 AccessDecisionManager 和 AccessDecisionVoters 适配 AuthorizationManager 的方式,用来兼容之前的代码,请点击这里。
至此,Spring Security 的授权架构介绍完毕,在这篇文章中,更多的是方法论,具体的使用细节将在后续文章中进行补充。