首页 慕课教程 Spring Security Spring Security Spring Security 异常回收

Spring Security 异常回收

1. 前言

上一节中我们介绍了 Spring Security 中内置的安全过滤器,本节将介绍 Spring Security 的另一个基础概念:「异常处理」。

这里所指异常是 Spring Security 在认证和鉴权过程中捕获的异常,也就是访问目标资源发生错误时的原因,Spring Security 以异常的形式处理「认证错误」和「权限错误」,并将结果传回请求方。

学习 Spring Security 的异常回收机制,有助于在开发和应用过程中排查问题。

2. 异常处理流程

Spring Security 的认证、授权异常在过滤器校验过程中产生,并在 ExceptionTranslationFilter 中接收并进行处理,其流程如下:

图片描述

图1. ExceptionTranslationFilter
  1. ExceptionTranslationFilter 过滤器首先像其他过滤器一样,调用过滤器链的执行方法 FilterChain.doFilter(request, response) 启动过滤处理;
  2. 如果当前的用户没有通过认证或者因为其他原因在执行过程中抛出了 AuthenticationException 异常,此时将开启「认证流程」:
    1. 清空 SecurityContextHolder 对象;
    2. 并将原始请求信息「request」保存到 RequestCache 对象中;
    3. 使用 AuthenticationEntryPoint 对象存储的认证地址,向客户端索要身份证明。例如,使用浏览器登录的用户,将浏览器地址重定向到 /login 或者回传一个 WWW-Authenticate 认证请求头。
  3. 如果当前用户身份信息已确认,但是没有访问权限,则会产生 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,并使用该类提供的 AuthenticationEntryPointAccessDeniedHandler 参数来配置异常处理:

  • 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 的第一个重要模块「认证」。