• SpringSecurity - 启动流程分析(十二)- ExceptionTranslationFilter 过滤器


    前言

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    查看 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    查看具体源码,可以看到,默认情况下,无论是 AuthenticationEntryPoint 还是 AccessDeniedHandler 都会返回 403 状态码。并且 AuthenticationEntryPointAuthenticationException 异常的处理类;AccessDeniedHandler AccessDeniedException 异常的处理类。

    分析

    SpringSecurity 中的异常可以分为两大类,一种是认证异常,一种是授权异常。

    1、AuthenticationException(认证异常)

    在这里插入图片描述

    2、AccessDeniedException(授权异常)

    在这里插入图片描述

    3、ExceptionTranslationFilter

    从上面分析中我们知道,ExceptionHandlingConfigurerconfigure() 方法调用了 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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 查看 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 查看 handleAccessDeniedException 逻辑
    private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
    			FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
    	// 判断是否是匿名用户访问
    	...
    		// 否则,处理异常,实现 AccessDeniedHandler 自定义处理逻辑
    		this.accessDeniedHandler.handle(request, response, exception);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    总结

    • 在过滤器链中,ExceptionTranslationFilter 的下一个过滤器是 FilterSecurityInterceptorFilterSecurityInterceptor 是处理授权问题的,这里抛出的异常,会被 ExceptionTranslationFilter 捕获,来让我们做自定义处理
    • 捕获到异常之后,通过 throwableAnalyzer.getFirstThrowableOfType 来判断是认证异常还是授权异常,如果都不是的话,抛出 ServletException 或者 RuntimeException
  • 相关阅读:
    el-select配合el-tree实现下拉选以及数据回显以及搜索
    CasA:用于点云 3D 目标检测的级联注意力网络
    人工智能文本生成器将如何影响写作行业
    使用OpenFlow和Ryu控制器实现网络交换机的软件定义网络(SDN)控制
    深度学习与目标检测:从卷积神经网络到YOLOv8概念介绍
    java 从 HDFS 读取数据到本地文件
    刷爆LeetCode 字节技术官亲码算法面试进阶神技太香了
    【Leetcode】1554. Strings Differ by One Character
    ffmpeg视频解码器的配置选项含义
    P272 小码君统统计字母频率-1
  • 原文地址:https://blog.csdn.net/qiaohao0206/article/details/126641432