1. 说明

SpringSecurity的实现原理是过滤器,它的执行时间比SpringMVC早,因此在执行SpringSecurity相关功能(如用户登录)时是使用不到SpringMVC的统一返回处理@ContrtollerAdvice + @ExceptionHandler的组合套装的。

2. SpringSecurity 实现 @ContrtollerAdvice增强

SpringSecurity想要实现@ControllerAdvice需要对其认证返回 的异常做自定义处理,并引用SpringMVC的异常处理器
其思路就是在返回扩展接口中调用SpringMVC的异常解析器来解析处理,这样就衔接上了SpringMVC的扩展

示例实现如下

2.1. 引入依赖

引入SpringSecurity和SpringMVC的相关依赖

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.2. 配置异常解析器

首先是创建一个异常处理类,处理SpringSecurity的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Component
public class SecurityAuthenticationExceptionResolverHandler implements AuthenticationEntryPoint, AuthenticationFailureHandler, AccessDeniedHandler {

private static final Logger logger = LoggerFactory.getLogger(SecurityAuthenticationExceptionResolverHandler.class);

@Autowired
HandlerExceptionResolver handlerExceptionResolver;

/**
* AuthenticationEntryPoint 的实现
* 处理未进入登录但后续需要验证登录的异常
*/
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
resolver(request, response, exception);
}

/**
* AccessDeniedHandler 的实现
* 访问异常的处理
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception) throws IOException, ServletException {
resolver(request, response, exception);
}

/**
* AuthenticationFailureHandler 的实现
* 认证失败的处理
*/
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
resolver(request, response, exception);
}

/**
* 具体解析器的处理逻辑
*/
private void resolver(HttpServletRequest request, HttpServletResponse response, Exception exception) {
//拿到容器中配置的HandlerExceptionResolver,并调用其解析逻辑,这个类本身就会扫描`@ControllerAdvice`并加入HandlerExceptionResolver中
ModelAndView modelAndView = handlerExceptionResolver.resolveException(request, response, null, exception);
//没有解析到,没有解析到那就默认返回
if (modelAndView == null) {
logger.warn("安全异常返回无处理解析器,按照默认返回");
response.setContentType("application/json;charset=utf-8");
try {
PrintWriter out = response.getWriter();
out.write("Security 安全框架返回异常,且没有配置返回解析,异常信息:" + exception.getMessage());
out.flush();
out.close();
} catch (IOException e) {
throw new AuthenticationServiceException("SecurityExceptionResolverHandler 解析返回异常", e);
}
}
}
}

然后再SecurityConfig的配置类中配置异常的处理,需配置三处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableConfigurationProperties({SecurityLoginProperties.class})
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
SecurityAuthenticationExceptionResolverHandler exceptionResolverHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.failureHandler(exceptionResolverHandler).and()
.exceptionHandling()
.accessDeniedHandler(exceptionResolverHandler)
.authenticationEntryPoint(exceptionResolverHandler)
.and()

;
}
}

配置异常增强

然后就可以像配置普通SpringMVC工程一样配置增强即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RestControllerAdvice
public class ExceptionAdviceHandler {
private static final Logger logger = LoggerFactory.getLogger(ExceptionAdviceHandler.class);

@ExceptionHandler(AccessDeniedException.class)
public Result accessDeniedException(Throwable t){
logger.error("访问异常:{}", t.getMessage(), t);
return Result.failed(500, t.getMessage());
}
@ExceptionHandler(AuthenticationException.class)
public Result authenticationException(Throwable t){
logger.error("授权异常:{}", t.getMessage(), t);
return Result.failed(500, t.getMessage());
}
@ExceptionHandler(Throwable.class)
public Result throwException(Throwable t){
logger.error("全局异常:{}", t.getMessage(), t);
return Result.failed(500, t.getMessage());
}

}

引用