Spring Security 异常回收
1. 前言
上一节中我们介绍了 Spring Security 中内置的安全过滤器,本节将介绍 Spring Security 的另一个基础概念:「异常处理」。
这里所指异常是 Spring Security 在认证和鉴权过程中捕获的异常,也就是访问目标资源发生错误时的原因,Spring Security 以异常的形式处理「认证错误」和「权限错误」,并将结果传回请求方。
学习 Spring Security 的异常回收机制,有助于在开发和应用过程中排查问题。
2. 异常处理流程
Spring Security 的认证、授权异常在过滤器校验过程中产生,并在 ExceptionTranslationFilter 中接收并进行处理,其流程如下:

图1. ExceptionTranslationFilter
ExceptionTranslationFilter过滤器首先像其他过滤器一样,调用过滤器链的执行方法FilterChain.doFilter(request, response)启动过滤处理;- 如果当前的用户没有通过认证或者因为其他原因在执行过程中抛出了 
AuthenticationException异常,此时将开启「认证流程」:- 清空 
SecurityContextHolder对象; - 并将原始请求信息「request」保存到 
RequestCache对象中; - 使用 
AuthenticationEntryPoint对象存储的认证地址,向客户端索要身份证明。例如,使用浏览器登录的用户,将浏览器地址重定向到/login或者回传一个WWW-Authenticate认证请求头。 
 - 清空 
 - 如果当前用户身份信息已确认,但是没有访问权限,则会产生 
AccessDeniedException异常,然后访问被拒绝。继续执行拒绝处理AccessDeniedHandler。 
假如认证过程中没有产生「认证异常」或者「权限异常」,ExceptionTranslationFilter 则不做任何处理。
3. 异常的种类

图2. 主要的异常种类
3.1 认证异常
认证异常是在认证阶段抛出的异常,其主要的实现类包括:
- AccountStatusException
 
出现在账户状态异常时候,比如认证凭据过期了、账户被锁定了等。
- ActiveDirectoryAuthenticationException
 
出现在 AD 域认证异常时。
- AuthenticationCancelledException
 
出现在 OpenID 认证时,认证状态被取消。
- AuthenticationCredentialsNotFoundException
 
出现在无法找到认证凭证时,即 SecurityContext 实例中找不到 Authentication 对象。
- AuthenticationServiceException
 
出现在认证时遇到了后台错误。
- BadCredentialsException
 
出现在凭据检查失败,比如账户被禁用时。
- InsufficientAuthenticationException
 
出现在以获得凭据,但凭据不被信任的情况。
- NonceExpiredException
 
出现在数字证书异常时。
- OAuth2AuthenticationException
 
出现在 OAuth2 认证异常时。
- PreAuthenticatedCredentialsNotFoundException
 
出现在预认证凭据未找到时。
- ProviderNotFoundException
 
出现在当前认证方式不被支持时。
- RememberMeAuthenticationException
 
出现在记住我认证失败时。
- Saml2AuthenticationException
 
出现在 Saml2 认证失败时。
- SessionAuthenticationException
 
出现在会话异常时,比如当前用户创建的会话已经超过系统容量。
- UsernameNotFoundException
 
出现在找不到用户时。
3.2 权限异常
权限异常是在访问资源阶段抛出的异常,其主要的实现类包括:
- AuthorizationServiceException
 
当鉴权请求无法完成或者,比如找不到目标方法时抛出此异常。
- org.springframework.security.web.server.csrf.CsrfException
 
当 「CsrfToken」异常或缺失时抛出此异常。
- org.springframework.security.web.csrf.CsrfException
 
当 「CsrfToken」异常或缺失时抛出此异常。
4. 自定义异常处理
当我们需要自定义异常处理时,需要在 HttpSecurity 对象的 exceptionHandling() 方法获取异常处理的配置入口 ExceptionHandlingConfigurer,并使用该类提供的 AuthenticationEntryPoint 和 AccessDeniedHandler 参数来配置异常处理:
AuthenticationEntryPoint该类用来统一处理AuthenticationException异常;AccessDeniedHandler该类用来统一处理AccessDeniedException异常。
4.1 自定义 AuthenticationEntryPoint
假设我们想统一异常 json 响应。
public class JSONAuthenticationEntryPoint implements AuthenticationEntryPoint {
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    // 实现个性化业务逻辑 ...
    // 配置返回值
    HashMap<String, String> map = new HashMap<>(2);
    map.put("uri", request.getRequestURI());
    map.put("msg", "认证失败");
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    response.setCharacterEncoding("utf-8");
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    ObjectMapper objectMapper = new ObjectMapper();
    String resBody = objectMapper.writeValueAsString(map);
    PrintWriter printWriter = response.getWriter();
    printWriter.print(resBody);
    printWriter.flush();
    printWriter.close();
  }
}
4.2 实现 AccessDeniedHandler
同样假设我们想统一异常 json 响应。
public class JSONAccessDeniedHandler implements AccessDeniedHandler {
  @Override
  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
    // 实现个性化业务逻辑 ...
    // 配置返回值
    HashMap<String, String> map = new HashMap<>(2);
    map.put("uri", request.getRequestURI());
    map.put("msg", "认证失败");
    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
    response.setCharacterEncoding("utf-8");
    response.setContentType(MediaType.APPLICATION_JSON_VALUE);
    ObjectMapper objectMapper = new ObjectMapper();
    String resBody = objectMapper.writeValueAsString(map);
    PrintWriter printWriter = response.getWriter();
    printWriter.print(resBody);
    printWriter.flush();
    printWriter.close();
  }
}
5. 小结
本节我们介绍了 Spring Security 中的异常回收机制:
- Spring Security 的异常处理是通过「安全过滤器」方式实现的;
 - Spring Security 的异常分为两大类,分别是「身份异常」和「权限异常」;
 - Spring Security 可以通过修改配置自定义异常处理。
 
至此,关于 Spring Security 基础部分就告一段了,下节开始我们进入应用环节,讨论 Spring Security 的第一个重要模块「认证」。
访问者可将本网站提供的内容或服务用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯本网站及相关权利人的合法权利。
本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站,邮箱:80764001@qq.com,予以删除。
