前言
- • ExceptionTranslationFilter是SpringSecurity中负责处理异常的过滤器
- • 这里的异常指的是下面两大异常
- • AuthenticationException:认证异常
- • AccessDeniedException:访问被拒绝异常
- • 这里的异常是由SpringSecurity中负责进行权限校验的FilterSecurityInterceptor抛出的
1. ExceptionHandlingConfigurer
- • ExceptionHandlingConfigurer是ExceptionTranslationFilter对应的配置类
- • 也是默认开启的配置类之一image.png
1.1 常用方法
• 配置类中的常用方法都是为了配置下面的对象
public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<ExceptionHandlingConfigurer<H>, H> {
private AuthenticationEntryPoint authenticationEntryPoint;
private AccessDeniedHandler accessDeniedHandler;
private LinkedHashMap<RequestMatcher, AuthenticationEntryPoint> defaultEntryPointMappings = new LinkedHashMap<>();
private LinkedHashMap<RequestMatcher, AccessDeniedHandler> defaultDeniedHandlerMappings = new LinkedHashMap<>();
}
• authenticationEntryPoint:身份验证入口点 是抛出认证异常才会执行的,比如说回到登录页的实现类LoginUrlAuthenticationEntryPoint
• accessDeniedHandler:捕获了访问被拒绝异常的处理器
• defaultEntryPointMappings和defaultDeniedHandlerMappings:存放不同请求路径的的访问被拒绝的多个处理器 key:请求匹配器,比如说匹配/user value:对应的访问被拒绝的处理器
1.2 configure(...)
public void configure(H http) {
//获得身份认证入口点
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
//创建处理异常的过滤器,还传入了请求缓存器
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint,
getRequestCache(http));
//获得访问被拒绝处理器
AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
//进行objectPostProcessor处理
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
//添加过滤器到httpSecurity中
http.addFilter(exceptionTranslationFilter);
}
• doFilter(...)方法的核心逻辑是try住下一个过滤器,而这个过滤器就是FilterSecurityInterceptor
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
chain.doFilter(request, response);
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
//尝试从堆栈跟踪中提取SpringSecurityException,不懂
Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);
//拿到认证异常
RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
//不是一个认证异常
if (securityException == null) {
securityException = (AccessDeniedException) this.throwableAnalyzer
.getFirstThrowableOfType(AccessDeniedException.class, causeChain);
}
//既不是认证异常也不是访问被拒绝异常,那就继续抛
if (securityException == null) {
rethrow(ex);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception "
+ "because the response is already committed.", ex);
}
//处理SpringSecurity异常
handleSpringSecurityException(request, response, chain, securityException);
}
}
• 而catch住的异常都会通过ThrowableAnalyzer转为认证异常和访问被拒绝异常,然后丢给handleSpringSecurityException(...)方法继续处理
• 再看handleSpringSecurityException(...)方法,这里就分别对于两个异常有不同的处理逻辑了
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, RuntimeException exception) throws IOException, ServletException {
//是一个认证异常
if (exception instanceof AuthenticationException) {
handleAuthenticationException(request, response, chain, (AuthenticationException) exception);
}
//是一个访问被拒绝异常
else if (exception instanceof AccessDeniedException) {
handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);
}
}
2.1 handleAuthenticationException(...)
private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AuthenticationException exception) throws ServletException, IOException {
this.logger.trace("Sending to authentication entry point since authentication failed", exception);
sendStartAuthentication(request, response, chain, exception);
}
/**
* 处理认证异常
*/
protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
//清除存储在线程级别的上下文策略的认证信息,HttpSession级别的会在SecurityContextPersistenceFilter的finally代码块中被更新
//因为现有的认证不再被认为有效
SecurityContext context = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(context);
//将当前的请求放入请求缓存器
//这样当重新登录后,还能将请求包装为这一次请求
this.requestCache.saveRequest(request, response);
//执行认证异常处理器
this.authenticationEntryPoint.commence(request, response, reason);
}
2.2 handleAccessDeniedException(...)
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
//当是匿名用户和记住我用户
if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
authentication), exception);
}
//还是当成一个认证异常处理
//表示需要完整登录(用用户名和密码登录)
sendStartAuthentication(request, response, chain,
new InsufficientAuthenticationException(
this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
if (logger.isTraceEnabled()) {
logger.trace(
LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
exception);
}
//调用访问被拒绝处理器,进行处理
this.accessDeniedHandler.handle(request, response, exception);
}
}