Spring Security3源码分析(15)-ExceptionTranslationFilter分析

ExceptionTranslationFilter过滤器对应的类路径为 

org.springframework.security.web.access.ExceptionTranslationFilter 

从类名就看出这个过滤器用于异常翻译的。但是从这个过滤器在filterchain中的位置来看,它仅仅处于倒数第三的位置(这个filter后面分为是FilterSecurityInterceptor、SwitchUserFilter),所以ExceptionTranslationFilter只能捕获到后面两个过滤器所抛出的异常。 

这里需要强调一下,spring security中的异常类基本上都继承RuntimeException。 

接着看ExceptionTranslationFilter执行过程 

Java代码  

  1. //doFilter拦截到请求时,不做处理。仅仅处理后面filter所抛出的异常  
  2. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  3.         throws IOException, ServletException {  
  4.     HttpServletRequest request = (HttpServletRequest) req;  
  5.     HttpServletResponse response = (HttpServletResponse) res;  
  6.   
  7.     try {  
  8.         chain.doFilter(request, response);  
  9.     }  
  10.     catch (IOException ex) {  
  11.         throw ex;  
  12.     }  
  13.     catch (Exception ex) {  
  14.         //这里主要是从异常堆栈中提取SpringSecurityException  
  15.         Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);  
  16.         RuntimeException ase = (AuthenticationException)  
  17.                 throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);  
  18.   
  19.         if (ase == null) {  
  20.             ase = (AccessDeniedException)throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);  
  21.         }  
  22.         //如果提取到安全异常,则进行处理  
  23.         if (ase != null) {  
  24.             handleException(request, response, chain, ase);  
  25.         } else {  
  26.             //没有安全异常,继续抛出  
  27.             // Rethrow ServletExceptions and RuntimeExceptions as-is  
  28.             if (ex instanceof ServletException) {  
  29.                 throw (ServletException) ex;  
  30.             }  
  31.             else if (ex instanceof RuntimeException) {  
  32.                 throw (RuntimeException) ex;  
  33.             }  
  34.             throw new RuntimeException(ex);  
  35.         }  
  36.     }  
  37. }  
  38. //处理安全异常  
  39. private void handleException(HttpServletRequest request, HttpServletResponse response, FilterChain chain,  
  40.         RuntimeException exception) throws IOException, ServletException {  
  41.     //如果是认证异常,由sendStartAuthentication处理  
  42.     if (exception instanceof AuthenticationException) {  
  43.         sendStartAuthentication(request, response, chain, (AuthenticationException) exception);  
  44.     }  
  45.     //如果是访问拒绝异常,由访问拒绝处理类的handle处理  
  46.     else if (exception instanceof AccessDeniedException) {  
  47.         if (authenticationTrustResolver.isAnonymous(SecurityContextHolder.getContext().getAuthentication())) {  
  48.             sendStartAuthentication(request, response, chain, new InsufficientAuthenticationException(  
  49.                     “Full authentication is required to access this resource”));  
  50.         }  
  51.         else {  
  52.             accessDeniedHandler.handle(request, response, (AccessDeniedException) exception);  
  53.         }  
  54.     }  
  55. }  

先分析如何处理认证异常 

Java代码  

  1. //处理认证异常  
  2. protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,  
  3.         AuthenticationException reason) throws ServletException, IOException {  
  4.     // SEC-112: Clear the SecurityContextHolder’s Authentication, as the  
  5.     // existing Authentication is no longer considered valid  
  6.     //首先把SecurityContext中的认证实体置空  
  7.     SecurityContextHolder.getContext().setAuthentication(null);  
  8.     //通过cache保存当前的请求信息(分析RequestCacheAwareFilter时再深入)  
  9.     requestCache.saveRequest(request, response);  
  10.     logger.debug(“Calling Authentication entry point.”);  
  11.     //由认证入口点开始处理  
  12.     authenticationEntryPoint.commence(request, response, reason);  
  13. }  

这里补充一下 

authenticationEntryPoint是由配置http标签时,通过什么认证入口来决定注入相应的入口点bean的。请看下面的对应关系列表 
form-login认证:LoginUrlAuthenticationEntryPoint 
http-basic认证:BasicAuthenticationEntryPoint 
openid-login认证:LoginUrlAuthenticationEntryPoint 
x509认证:Http403ForbiddenEntryPoint
 

就不一一分析每个EntryPoint了,着重看一下LoginUrlAuthenticationEntryPoint 

Java代码  

  1. //主要目的是完成跳转任务  
  2.  //创建该bean时,只注入了loginFormUrl属性,其他类变量均为默认值  
  3. public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)  
  4.         throws IOException, ServletException {  
  5.     HttpServletRequest httpRequest = (HttpServletRequest) request;  
  6.     HttpServletResponse httpResponse = (HttpServletResponse) response;  
  7.   
  8.     String redirectUrl = null;  
  9.     //默认为false  
  10.     if (useForward) {  
  11.         if (forceHttps && “http”.equals(request.getScheme())) {  
  12.             redirectUrl = buildHttpsRedirectUrlForRequest(httpRequest);  
  13.         }  
  14.   
  15.         if (redirectUrl == null) {  
  16.             String loginForm = determineUrlToUseForThisRequest(httpRequest, httpResponse, authException);  
  17.             RequestDispatcher dispatcher = httpRequest.getRequestDispatcher(loginForm);  
  18.             dispatcher.forward(request, response);  
  19.             return;  
  20.         }  
  21.     } else {  
  22.         //返回的url为loginFormUrl配置的值,如果未配置,跳转到默认登录页面/spring_security_login  
  23.         redirectUrl = buildRedirectUrlToLoginPage(httpRequest, httpResponse, authException);  
  24.   
  25.     }  
  26.     redirectStrategy.sendRedirect(httpRequest, httpResponse, redirectUrl);  
  27. }  

接着分析访问拒绝类异常的处理过程,看AccessDeniedHandlerImpl的handle方法 

Java代码  

  1. public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)  
  2.         throws IOException, ServletException {  
  3.     if (!response.isCommitted()) {  
  4.         //如果配置了access-denied-page属性,跳转到指定的url  
  5.         if (errorPage != null) {  
  6.             // Put exception into request scope (perhaps of use to a view)  
  7.             request.setAttribute(SPRING_SECURITY_ACCESS_DENIED_EXCEPTION_KEY, accessDeniedException);  
  8.   
  9.             // Set the 403 status code.  
  10.             response.setStatus(HttpServletResponse.SC_FORBIDDEN);  
  11.   
  12.             // forward to error page.  
  13.             RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);  
  14.             dispatcher.forward(request, response);  
  15.         //如果没有配置,则直接响应403禁止访问的错误信息到浏览器端  
  16.         } else {  
  17.             response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());  
  18.         }  
  19.     }  
  20. }  

通过以上分析,可以大体上认识到ExceptionTranslationFilter主要拦截两类安全异常:认证异常、访问拒绝异常。而且仅仅是捕获FilterSecurityInterceptor、SwitchUserFilter以及自定义拦截器的异常。所以在自定义拦截器时,需要注意在链中的顺序。 

在上面分析过程中,有requestCache.saveRequest(request, response);的语句,具体requestCache的用途下篇分析。

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/benjamin_whx/article/details/39204709
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞