一、概述
在本教程中,我们将学习如何使用@ExceptionHandler
和@ControllerAdvice.
**控制器通知是一个拦截器,**它允许我们在整个应用程序中使用相同的异常处理。
2. Spring 安全异常
AuthenticationException
和AccessDeniedException
等Spring 安全核心异常是运行时异常。由于这些异常是由DispatcherServlet
后面的身份验证过滤器引发的,并且在调用控制器方法之前,@ControllerAdvice
ControllerAdvice 将无法捕获这些异常。
Spring 安全异常可以通过添加自定义过滤器和构造响应体来直接处理。要通过@ExceptionHandler
和@ControllerAdvice,
我们需要AuthenticationEntryPoint
的自定义实现。AuthenticationEntryPoint
用于发送从客户端请求凭据的HTTP 响应。尽管安全入口点有多种内置实现,但我们需要编写自定义实现来发送自定义响应消息。
首先,让我们看看在不使用@ExceptionHandler
的情况下全局处理安全异常。
3.没有@ExceptionHandler
Spring 安全异常从AuthenticationEntryPoint
开始。让我们为AuthenticationEntryPoint
编写一个实现来拦截安全异常。
3.1。配置AuthenticationEntryPoint
让我们实现AuthenticationEntryPoint
并覆盖commence()
方法:
@Component("customAuthenticationEntryPoint")
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(), "Authentication failed");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream responseStream = response.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(responseStream, re);
responseStream.flush();
}
}
在这里,我们使用ObjectMapper
作为响应正文的消息转换器。
3.2.配置SecurityConfig
配置
接下来,让我们配置SecurityConfig
来拦截认证路径。在这里,我们将配置“ /login
”作为上述实现的路径。此外,我们将为'admin' 用户配置'ADMIN' 角色:
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customAuthenticationEntryPoint")
AuthenticationEntryPoint authEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login")
.and()
.authorizeRequests()
.anyRequest()
.hasRole("ADMIN")
.and()
.httpBasic()
.and()
.exceptionHandling()
.authenticationEntryPoint(authEntryPoint);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
}
}
3.3.配置休息控制器
现在,让我们编写一个监听这个端点'/login'的休息控制器:
@PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<RestResponse> login() {
return ResponseEntity.ok(new RestResponse("Success"));
}
3.4.测试
最后,让我们用模拟测试来测试这个端点。
首先,让我们编写一个成功认证的测试用例:
@Test
@WithMockUser(username = "admin", roles = { "ADMIN" })
public void whenUserAccessLogin_shouldSucceed() throws Exception {
mvc.perform(formLogin("/login").user("username", "admin")
.password("password", "password")
.acceptMediaType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
接下来,我们来看一个认证失败的场景:
@Test
public void whenUserAccessWithWrongCredentialsWithDelegatedEntryPoint_shouldFail() throws Exception {
RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(), "Authentication failed");
mvc.perform(formLogin("/login").user("username", "admin")
.password("password", "wrong")
.acceptMediaType(MediaType.APPLICATION_JSON))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.errorMessage", is(re.getErrorMessage())));
}
现在,让我们看看如何使用@ControllerAdvice
和@ExceptionHandler
实现相同的效果。
4. 使用@ExceptionHandler
这种方法允许我们使用完全相同的异常处理技术,但在控制器建议中以更清晰、更好的方式使用带有@ExceptionHandler
注释的方法。
4.1。配置AuthenticationEntryPoint
与上述方法类似,我们将实现AuthenticationEntryPoint
,然后将异常处理程序委托给HandlerExceptionResolver
:
@Component("delegatedAuthenticationEntryPoint")
public class DelegatedAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Autowired
@Qualifier("handlerExceptionResolver")
private HandlerExceptionResolver resolver;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException, ServletException {
resolver.resolveException(request, response, null, authException);
}
}
在这里,我们注入了DefaultHandlerExceptionResolver
并将处理程序委托给此解析器。现在可以使用带有异常处理程序方法的控制器建议来处理此安全异常。
4.2.配置ExceptionHandler
现在,对于异常处理程序的主要配置,我们将扩展ResponseEntityExceptionHandler
并使用@ControllerAdvice
注释这个类:
@ControllerAdvice
public class DefaultExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler({ AuthenticationException.class })
@ResponseBody
public ResponseEntity<RestError> handleAuthenticationException(Exception ex) {
RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(),
"Authentication failed at controller advice");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(re);
}
}
4.3.配置SecurityConfig
配置
现在,让我们为这个委托的身份验证入口点编写一个安全配置:
@Configuration
@EnableWebSecurity
public class DelegatedSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("delegatedAuthenticationEntryPoint")
AuthenticationEntryPoint authEntryPoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/login-handler")
.and()
.authorizeRequests()
.anyRequest()
.hasRole("ADMIN")
.and()
.httpBasic()
.and()
.exceptionHandling()
.authenticationEntryPoint(authEntryPoint);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
}
}
对于“ /login-handler
”端点,我们已经使用上面实现的DelegatedAuthenticationEntryPoint
配置了异常处理程序。
4.4.配置休息控制器
让我们为' /login-handler
' 端点配置剩余控制器:
@PostMapping(value = "/login-handler", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<RestResponse> loginWithExceptionHandler() {
return ResponseEntity.ok(new RestResponse("Success"));
}
4.5.测试
现在让我们测试这个端点:
@Test
@WithMockUser(username = "admin", roles = { "ADMIN" })
public void whenUserAccessLogin_shouldSucceed() throws Exception {
mvc.perform(formLogin("/login-handler").user("username", "admin")
.password("password", "password")
.acceptMediaType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
@Test
public void whenUserAccessWithWrongCredentialsWithDelegatedEntryPoint_shouldFail() throws Exception {
RestError re = new RestError(HttpStatus.UNAUTHORIZED.toString(), "Authentication failed at controller advice");
mvc.perform(formLogin("/login-handler").user("username", "admin")
.password("password", "wrong")
.acceptMediaType(MediaType.APPLICATION_JSON))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.errorMessage", is(re.getErrorMessage())));
}
在成功测试中,我们使用预配置的用户名和密码测试了端点。在失败测试中,我们验证了响应正文中状态代码和错误消息的响应。
5. 结论
在本文中,我们学习了如何**使用@ExceptionHandler
全局处理Spring Security 异常**。此外,我们创建了一个功能齐全的示例,帮助我们理解所解释的概念。
0 评论