目录

一、异常信息类

二、自定义异常处理配置

三、ExceptionTranslationFilter 原理分析


一、异常信息类

Spring Security 中异常主要分为两大类:

  • AuthenticationException:认证异常
  • AccessDeniedException:授权异常

        其中认证所涉及异常类型比较多,默认提供的异常类型如下:

        7ab7cf166495bc67e6f700f5e97de8ed.png

        相比于认证异常,权限异常类就要少了很多,默认提供的权限异常如下:

        在实际项目开发中,如果默认提供异常无法满足需求时,就需要根据实际需要来自定义异常类。 

二、自定义异常处理配置

        首先异常处理设计到两个 Handler 进行处理 ,一个是处理认证异常的Handler处理器 AuthenticationEntryPoint,一个是授权异常的Handler处理器 AccessDeniedHandler

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .mvcMatchers("/index").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .exceptionHandling()
                // 认证异常处理handler 使用的 lambda的内部的实现
                .authenticationEntryPoint((request,response,authenticationException)->{
                    Map<String,Object> rs = new HashMap<>();
                    rs.put("code",401);
                    rs.put("msg","尚未认证!");
                    ObjectMapper objectMapper = new ObjectMapper();
                    String json = objectMapper.writeValueAsString(rs);
                    response.setStatus(200);
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    response.getWriter().println(json);

                })
             // 授权异常处理handler 使用的 lambda的内部的实现
                .accessDeniedHandler((request,response,accessDeniedException)->{
                    Map<String,Object> rs = new HashMap<>();
                    rs.put("code",401);
                    rs.put("msg","没有权限!");
                    ObjectMapper objectMapper = new ObjectMapper();
                    String json = objectMapper.writeValueAsString(rs);
                    response.setStatus(200);
                    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
                    response.getWriter().println(json);
                })
                .and()
                .csrf().disable();

    }
}

三、ExceptionTranslationFilter 原理分析

  1. ExceptionTranslationFilter 的doFilter 处理器它的作用是监听所有的访问状态。 
    1. 直接方法,监听执行是否出现异

    2. 如果抛出的是 IO 异常信息则直接抛出,不处理。

    3. 通过异常选择器进行的异常的信息的过滤

    4. 尝试去获取认证异常类型的异常对象,如果没有获取成功,则会去尝试获取授权类型的异常对象,如果获取两个异常类型其中之一,则会去执行对该异常的处理,否则则会报出去。  
        
    5. 	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      			throws IOException, ServletException {
      		HttpServletRequest request = (HttpServletRequest) req;
      		HttpServletResponse response = (HttpServletResponse) res;
      
      		// 1.直接放行,监听执行是否出现异常
      		try {
      			chain.doFilter(request, response);
      
      			logger.debug("Chain processed normally");
      		}
      		// 2.放行 IO 异常
      		catch (IOException ex) {
      			throw ex;
      		}
      		// 3.处理异常
      		catch (Exception ex) {
      			// Try to extract a SpringSecurityException from the stacktrace
      			//  4.尝试从stacktrace中提取SpringSecurityException 
      			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
      			// 5.从异常选择器中获取的第一个 AuthenticationException 类型的 异常
      			RuntimeException ase = (AuthenticationException) throwableAnalyzer
      					.getFirstThrowableOfType(AuthenticationException.class, causeChain);
      			// 判断是否有取出异常值
      			if (ase == null) {
      				// 再次尝试获取授权异常
      				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
      						AccessDeniedException.class, causeChain);
      			}
      			// 异常信息不为null
      			if (ase != null) {
      				// 判断是否委托
      				if (response.isCommitted()) {
      					throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
      				}
      				// 异常的处理
      				handleSpringSecurityException(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;
      				}
      
      				// Wrap other Exceptions. This shouldn't actually happen
      				// as we've already covered all the possibilities for doFilter
      				throw new RuntimeException(ex);
      			}
      		}
      	}

  2. 真正出处理异常的方法 handleSpringSecurityException       

                

  •  1. 首先判断是否是认证相关异常,如果是认证相关异常,则会调用 sendStartAuthentication 方法,将认证信息移除,并调用 AuthenticationEntryPoint commence 进行结果的处理
  • 2. 如果不是认证相关异常,则会获取当前认证信息,根据认证信息的类型去判断是否 匿名用户 或者是 记住我的用户 类型 如果是则会调用 sendStartAuthentication 的 认证异常处理器进行处理,否则则会调用 accessDeniedHandler.handle 的授权异常处理器进行处理。
	private void handleSpringSecurityException(HttpServletRequest request,
			HttpServletResponse response, FilterChain chain, RuntimeException exception)
			throws IOException, ServletException {
		
		// 判断异常是否 认证异常
		if (exception instanceof AuthenticationException) {
			logger.debug(
					"Authentication exception occurred; redirecting to authentication entry point",
					exception);
			// 开始发出异常信息给响应者
			sendStartAuthentication(request, response, chain,
					(AuthenticationException) exception);
		}

		// 判断异常是否 授权异常类型 
		else if (exception instanceof AccessDeniedException) {
			// 如果是授权异常,那么是有认证信息,则取出认证信息
			Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			// 判断是否匿名用户,或者是 记住我的用户信息
			if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
				logger.debug(
						"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
						exception);
				// 开始发出异常信息给响应者
				sendStartAuthentication(
						request,
						response,
						chain,
						new InsufficientAuthenticationException(
							messages.getMessage(
								"ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
			}
			else {
				// 如果都不是以上的用户信息,则交给 授权处理器
				logger.debug(
						"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
						exception);
				// 授权处理器去执行
				accessDeniedHandler.handle(request, response,
						(AccessDeniedException) exception);
			}
		}
	}
  • 3.认证异常的处理方法 ExceptionTranslationFilter 的sendStartAuthentication
    • 该方法是处理 认证失败的异常

    • 1. 将当前的用户信息情况
    • 2.将请求和响应对象放入session中
    • 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);
		requestCache.saveRequest(request, response);
		logger.debug("Calling Authentication entry point.");
		authenticationEntryPoint.commence(request, response, reason);
	}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐