Spring Security 异常回收
1. 前言
上一节中我们介绍了 Spring Security 中内置的安全过滤器,本节将介绍 Spring Security 的另一个基础概念:「异常处理」。
这里所指异常是 Spring Security 在认证和鉴权过程中捕获的异常,也就是访问目标资源发生错误时的原因,Spring Security 以异常的形式处理「认证错误」和「权限错误」,并将结果传回请求方。
学习 Spring Security 的异常回收机制,有助于在开发和应用过程中排查问题。
2. 异常处理流程
Spring Security 的认证、授权异常在过滤器校验过程中产生,并在 ExceptionTranslationFilter
中接收并进行处理,其流程如下:
ExceptionTranslationFilter
过滤器首先像其他过滤器一样,调用过滤器链的执行方法FilterChain.doFilter(request, response)
启动过滤处理;- 如果当前的用户没有通过认证或者因为其他原因在执行过程中抛出了
AuthenticationException
异常,此时将开启「认证流程」:- 清空
SecurityContextHolder
对象; - 并将原始请求信息「request」保存到
RequestCache
对象中; - 使用
AuthenticationEntryPoint
对象存储的认证地址,向客户端索要身份证明。例如,使用浏览器登录的用户,将浏览器地址重定向到/login
或者回传一个WWW-Authenticate
认证请求头。
- 清空
- 如果当前用户身份信息已确认,但是没有访问权限,则会产生
AccessDeniedException
异常,然后访问被拒绝。继续执行拒绝处理AccessDeniedHandler
。
假如认证过程中没有产生「认证异常」或者「权限异常」,ExceptionTranslationFilter
则不做任何处理。
3. 异常的种类
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 的第一个重要模块「认证」。