Spring-security源码-Filter之ExceptionTranslationFilter(十八)
统一的异常处理过滤器,我们可以处理统一的身份认证或者授权的异常 比如未登录访问登录页面 需要认证权限的地方
通过HttpSecurity 可以指定
通过org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer
private void applyDefaultConfiguration(HttpSecurity http) throws Exception { //http本质也是build 这里都是配置默认的config configure add CsrfConfigurer http.csrf(); //默认增加一个WebAsyncManagerIntegrationFilter http.addFilter(new WebAsyncManagerIntegrationFilter()); //configures add ExceptionHandlingConfigurer http.exceptionHandling(); //configures add HeadersConfigurer http.headers(); //configures add SessionManagementConfigurer http.sessionManagement(); //configure add SecurityContextConfigurer http.securityContext(); //configure add RequestCacheConfigurer http.requestCache(); ///configure add AnonymousConfigurer http.anonymous(); ///configure add ServletApiConfigurer http.servletApi(); //configure DefaultLoginPageConfigurer http.apply(new DefaultLoginPageConfigurer<>()); //configure LogoutConfigurer http.logout(); }
org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer#configure
@Override public void configure(H http) { //身份验证入口点(驱动应用开始进行身份验证),用于启动身份验证方案(默认:Http403ForbiddenEntryPoint) AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http); ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter(entryPoint, getRequestCache(http)); //获得处理异常的handle AccessDeniedHandler deniedHandler = getAccessDeniedHandler(http); exceptionTranslationFilter.setAccessDeniedHandler(deniedHandler); //autowired注入 exceptionTranslationFilter = postProcess(exceptionTranslationFilter); //加入httpConfig http.addFilter(exceptionTranslationFilter); }
org.springframework.security.web.access.ExceptionTranslationFilter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { //tryCath捕获异常 try { chain.doFilter(request, response); } catch (IOException ex) { throw ex; } catch (Exception ex) { 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); } //<1>处理异常 handleSpringSecurityException(request, response, chain, securityException); } }
<1>
org.springframework.security.web.access.ExceptionTranslationFilter#handleSpringSecurityException
private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) throws IOException, ServletException { //授权类异常 if (exception instanceof AuthenticationException) { //<2> handleAuthenticationException(request, response, chain, (AuthenticationException) exception); } //身份认证异常 else if (exception instanceof AccessDeniedException) { // <4> handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception); } }
<2>
private void handleAuthenticationException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException exception) throws ServletException, IOException { //<3> sendStartAuthentication(request, response, chain, exception); }
<3>
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 SecurityContextHolder.getContext().setAuthentication(null); //保存当前Request的请求信息,例如:header、cookie、path等,以便在用户进行身份验证后可以检索和重用该请求 requestCache.saveRequest(request, response); logger.debug("Calling Authentication entry point."); //开始启动验证(例如通过重定向到登录界面) authenticationEntryPoint.commence(request, response, reason); }
<4>
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AccessDeniedException exception) throws ServletException, IOException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); /** * 判断是是否是记住登录或者是未登录authenticationTrustResolver * authenticationTrustResolver主要是判断authentication是否是AnonymousAuthenticationToken * 或者RememberMeAuthenticationToken 类型 */ 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); } //交由accessDeniedHandler 处理 this.accessDeniedHandler.handle(request, response, 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 SecurityContextHolder.getContext().setAuthentication(null); this.requestCache.saveRequest(request, response); //最终交给authenticationEntryPoint处理 this.authenticationEntryPoint.commence(request, response, reason); }