org.springframework.security.web.access.ExceptionTranslationFilter
从类名就看出这个过滤器用于异常翻译的。但是从这个过滤器在filterchain中的位置来看,它仅仅处于倒数第三的位置(这个filter后面分为是FilterSecurityInterceptor、SwitchUserFilter),所以ExceptionTranslationFilter只能捕获到后面两个过滤器所抛出的异常。
这里需要强调一下,spring security中的异常类基本上都继承RuntimeException。
接着看ExceptionTranslationFilter执行过程
//doFilter拦截到请求时,不做处理。仅仅处理后面filter所抛出的异常 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); } catch (IOException ex) { throw ex; } catch (Exception ex) { //这里主要是从异常堆栈中提取SpringSecurityException Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) { ase = (AccessDeniedException)throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain); } //如果提取到安全异常,则进行处理 if (ase != null) { handleException(request, response, chain, ase); } else { //没有安全异常,继续抛出 // Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } throw new RuntimeException(ex); } } } //处理安全异常 private void handleException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { //如果是认证异常,由sendStartAuthentication处理 if (exception instanceof AuthenticationException) { sendStartAuthentication(request, response, chain, (AuthenticationException) exception); } //如果是访问拒绝异常,由访问拒绝处理类的handle处理 else if (exception instanceof AccessDeniedException) { if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) { sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException( "Full authentication is required to access this resource")); } else { accessDeniedHandler.handle(request, response, (AccessDeniedException) 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 //首先把SecurityContext中的认证实体置空 SecurityContextHolder.getContext().setAuthentication(null); //通过cache保存当前的请求信息(分析RequestCacheAwareFilter时再深入) requestCache.saveRequest(request, response); logger.debug("Calling Authentication entry point."); //由认证入口点开始处理 authenticationEntryPoint.commence(request, response, reason); }
这里补充一下
authenticationEntryPoint是由配置http标签时,通过什么认证入口来决定注入相应的入口点bean的。请看下面的对应关系列表
form-login认证:LoginUrlAuthenticationEntryPoint
http-basic认证:BasicAuthenticationEntryPoint
openid-login认证:LoginUrlAuthenticationEntryPoint
x509认证:Http403ForbiddenEntryPoint
就不一一分析每个EntryPoint了,着重看一下LoginUrlAuthenticationEntryPoint
//主要目的是完成跳转任务 //创建该bean时,只注入了loginFormUrl属性,其他类变量均为默认值 public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; String redirectUrl = null; //默认为false if (useForward) { if (forceHttps && "http".equals(request.getScheme())) { redirectUrl = buildHttpsRedirectUrlForRequest(httpRequest); } if (redirectUrl == null) { String loginForm = determineUrlToUseForThisRequest(httpRequest, httpResponse, authException); RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginForm); dispatcher.forward(request, response); return; } } else { //返回的url为loginFormUrl配置的值,如果未配置,跳转到默认登录页面/spring_security_login redirectUrl = buildRedirectUrlToLoginPage(httpRequest, httpResponse, authException); } redirectStrategy.sendRedirect(httpRequest, httpResponse, redirectUrl); }
接着分析访问拒绝类异常的处理过程,看AccessDeniedHandlerImpl的handle方法
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { if (!response.isCommitted()) { //如果配置了access-denied-page属性,跳转到指定的url if (errorPage != null) { // Put exception into request scope (perhaps of use to a view) request.setAttribute(SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY, accessDeniedException); // Set the 403 status code. response.setStatus(HttpServletResponse.SC_FORBIDDEN); // forward to error page. RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage); dispatcher.forward(request, response); //如果没有配置,则直接响应403禁止访问的错误信息到浏览器端 } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage()); } } }
通过以上分析,可以大体上认识到ExceptionTranslationFilter主要拦截两类安全异常:认证异常、访问拒绝异常。而且仅仅是捕获FilterSecurityInterceptor、SwitchUserFilter以及自定义拦截器的异常。所以在自定义拦截器时,需要注意在链中的顺序。
在上面分析过程中,有requestCache.saveRequest(request, response);的语句,具体requestCache的用途下篇分析。