ExceptionTranslationFilter异常转换过滤器位于整个springSecurityFilterChain的后方,用来转换整个链路中出现的异常,将其转化,顾名思义,转化以意味本身并不处理。一般其只处理两大类异常:AccessDeniedException访问异常和AuthenticationException认证异常。
这个过滤器非常重要,因为它将Java中的异常和HTTP的响应连接在了一起,这样在处理异常时,我们不用考虑密码错误该跳到什么页面,账号锁定该如何,只需要关注自己的业务逻辑,抛出相应的异常便可。如果该过滤器检测到AuthenticationException,则将会交给内部的AuthenticationEntryPoint去处理,如果检测到AccessDeniedException,需要先判断当前用户是不是匿名用户,如果是匿名访问,则和前面一样运行AuthenticationEntryPoint,否则会委托给AccessDeniedHandler去处理,而AccessDeniedHandler的默认实现,是AccessDeniedHandlerImpl。所以ExceptionTranslationFilter内部的AuthenticationEntryPoint是至关重要的,顾名思义:认证的入口点。
源码分析
public class ExceptionTranslationFilter extends GenericFilterBean {
//处理异常转换的核心方法
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
if (exception instanceof AuthenticationException) {
//重定向到登录端点
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
//重定向到登录端点
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
"Full authentication is required to access this resource"));
}
else {
//交给accessDeniedHandler处理
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
}
剩下的便是要搞懂AuthenticationEntryPoint和AccessDeniedHandler就可以了。
选择了几个常用的登录端点,以其中第一个为例来介绍,看名字就能猜到是认证失败之后,让用户跳转到登录页面。还记得我们一开始怎么配置表单登录页面的吗?
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()//FormLoginConfigurer
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
我们顺着formLogin返回的FormLoginConfigurer往下找,看看能发现什么,最终在FormLoginConfigurer的父类AbstractAuthenticationFilterConfigurer中有了不小的收获:
public abstract class AbstractAuthenticationFilterConfigurer extends ...{
...
//formLogin不出所料配置了AuthenticationEntryPoint
private LoginUrlAuthenticationEntryPoint authenticationEntryPoint;
//认证失败的处理器
private AuthenticationFailureHandler failureHandler;
...
}
具体如何配置的就不看了,我们得出了结论,formLogin()配置了之后最起码做了两件事,其一,为UsernamePasswordAuthenticationFilter设置了相关的配置,其二配置了AuthenticationEntryPoint。
登录端点还有Http401AuthenticationEntryPoint,Http403ForbiddenEntryPoint这些都是很简单的实现,有时候我们访问受限页面,又没有配置登录,就看到了一个空荡荡的默认错误页面,上面显示着401,403,就是这两个入口起了作用。
还剩下一个AccessDeniedHandler访问决策器未被讲解,简单提一下:AccessDeniedHandlerImpl这个默认实现类会根据errorPage和状态码来判断,最终决定跳转的页面
org.springframework.security.web.access.AccessDeniedHandlerImpl#handle
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
if (!response.isCommitted()) {
if (errorPage != null) {
// Put exception into request scope (perhaps of use to a view)
request.setAttribute(WebAttributes.ACCESS_DENIED_403,
accessDeniedException);
// Set the 403 status code.
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
// forward to error page.
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
}
else {
response.sendError(HttpServletResponse.SC_FORBIDDEN,
accessDeniedException.getMessage());
}
}
}