spring Security 异常处理
spring security前后端分离 异常处理 !
·
目录
三、ExceptionTranslationFilter 原理分析
一、异常信息类
Spring Security 中异常主要分为两大类:
- AuthenticationException:认证异常
- AccessDeniedException:授权异常
其中认证所涉及异常类型比较多,默认提供的异常类型如下:
相比于认证异常,权限异常类就要少了很多,默认提供的权限异常如下:
在实际项目开发中,如果默认提供异常无法满足需求时,就需要根据实际需要来自定义异常类。
二、自定义异常处理配置
首先异常处理设计到两个 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 原理分析
- ExceptionTranslationFilter 的doFilter 处理器它的作用是监听所有的访问状态。
-
直接方法,监听执行是否出现异
-
如果抛出的是 IO 异常信息则直接抛出,不处理。
-
通过异常选择器进行的异常的信息的过滤
- 尝试去获取认证异常类型的异常对象,如果没有获取成功,则会去尝试获取授权类型的异常对象,如果获取两个异常类型其中之一,则会去执行对该异常的处理,否则则会报出去。
-
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); } } }
-
- 真正出处理异常的方法 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);
}
更多推荐
已为社区贡献2条内容
所有评论(0)