前言
在 SpringSecurity - 启动流程分析(四)- 默认过滤器 这篇文章中,我们知道了 ExceptionTranslationFilter
是默认提供的,排序在倒数第 2
位的过滤器,接下来我们就来看一下它的作用是什么。
概述
ExceptionTranslationFilter
字面意思是:异常转换过滤器
。
在 HttpSecurity
中的配置方法为:
public ExceptionHandlingConfigurer<HttpSecurity> exceptionHandling() throws Exception {
return getOrApply(new ExceptionHandlingConfigurer<>());
}
// 可以接收一个函数式接口来自定义一些信息
public HttpSecurity exceptionHandling(
Customizer<ExceptionHandlingConfigurer<HttpSecurity>> exceptionHandlingCustomizer) throws Exception {
exceptionHandlingCustomizer.customize(getOrApply(new ExceptionHandlingConfigurer<>()));
return HttpSecurity.this;
}
查看 ExceptionHandlingConfigurer
配置类的核心方法 configure()
方法:
@Override
public void configure(H http) {
// 获取 AuthenticationEntryPoint
AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http);
// 核心流程,配置 ExceptionTranslationFilter
ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint,
getRequestCache(http));
// 获取 AccessDeniedHandler
AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http);
exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler);
exceptionTranslationFilter = postProcess(exceptionTranslationFilter);
http.addFilter(exceptionTranslationFilter);
}
AccessDeniedHandler getAccessDeniedHandler(H http) {
AccessDeniedHandler deniedHandler = this.accessDeniedHandler;
if (deniedHandler == null) {
// 如果没有配置,会创建默认的 AccessDeniedHandler
deniedHandler = createDefaultDeniedHandler(http);
}
return deniedHandler;
}
AuthenticationEntryPoint getAuthenticationEntryPoint(H http) {
AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint;
if (entryPoint == null) {
// 如果没有配置,会创建默认的 AuthenticationEntryPoint
entryPoint = createDefaultEntryPoint(http);
}
return entryPoint;
}
查看具体源码,可以看到,默认情况下,无论是
AuthenticationEntryPoint
还是AccessDeniedHandler
都会返回403
状态码。并且AuthenticationEntryPoint
是AuthenticationException
异常的处理类;AccessDeniedHandler
是AccessDeniedException
异常的处理类。
分析
SpringSecurity
中的异常可以分为两大类,一种是认证异常,一种是授权异常。
1、AuthenticationException
(认证异常)
2、AccessDeniedException
(授权异常)
3、ExceptionTranslationFilter
从上面分析中我们知道,ExceptionHandlingConfigurer
的 configure()
方法调用了 ExceptionTranslationFilter
的构造方法,传递了 AuthenticationEntryPoint
参数。查看核心方法 doFilter()
方法:
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) {
// Try to extract a SpringSecurityException from the stacktrace
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) {
// 不属于 Security 的两种异常的话在这里抛出
rethrow(ex);
}
if (response.isCommitted()) {
throw new ServletException("Unable to handle the Spring Security Exception "
+ "because the response is already committed.", ex);
}
// 处理异常,这里的 securityException 已经处理过,只可能是上面两种
handleSpringSecurityException(request, response, chain, securityException);
}
}
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);
}
}
- 查看
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 {
// SEC-112: Clear the SecurityContextHolder's Authentication, as the
// existing Authentication is no longer considered valid
// 清除 Authentication 信息
SecurityContext context = SecurityContextHolder.createEmptyContext();
SecurityContextHolder.setContext(context);
this.requestCache.saveRequest(request, response);
// 核心流程,也是我们可以实现 AuthenticationEntryPoint 自定义处理逻辑的地方
this.authenticationEntryPoint.commence(request, response, reason);
}
- 查看
handleAccessDeniedException
逻辑
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
// 判断是否是匿名用户访问
...
// 否则,处理异常,实现 AccessDeniedHandler 自定义处理逻辑
this.accessDeniedHandler.handle(request, response, exception);
}
}
总结
- 在过滤器链中,
ExceptionTranslationFilter
的下一个过滤器是FilterSecurityInterceptor
,FilterSecurityInterceptor
是处理授权问题的,这里抛出的异常,会被ExceptionTranslationFilter
捕获,来让我们做自定义处理 - 捕获到异常之后,通过
throwableAnalyzer.getFirstThrowableOfType
来判断是认证异常还是授权异常,如果都不是的话,抛出ServletException
或者RuntimeException