How to handle custom JwtAuthException Spring security
У меня есть JwtTokenProvider:
@Component
@PropertySource("classpath:jwt.properties")
@RequiredArgsConstructor
public class JwtTokenProvider {
//some code ...
public boolean validateToken(String token) {
try {
Jws<Claims> claimsJws = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token);
return !claimsJws.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException e) {
throw new JwtAuthenticationException("JWT token expired or invalid!");
}
}
}
Это обычный компонент, который, получается, выбрасывает исключение, если токен "протух"
Понятно, что, если обратиться к серверу с попорченным токеном, он выбросит исключение - но с ошибкой 500. Как перехватить его так, чтобы можно было возвращать 401? И только конкретно это исключение, 403 мне необходимо сохранить
Вот некоторый сопутствующие классы :
- Фильтр
@RequiredArgsConstructor
public class JwtTokenFilter extends GenericFilterBean {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String token = jwtTokenProvider.retrieveToken((HttpServletRequest) servletRequest);
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
2.Конфиг
@RequiredArgsConstructor
public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final JwtTokenProvider provider;
@Override
public void configure(HttpSecurity builder) throws Exception {
JwtTokenFilter filter = new JwtTokenFilter(provider);
builder.addFilterBefore(filter, UsernamePasswordAuthenticationFilter.class);
}
}
==========================
@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = {SecurityPackageMarker.class})
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtTokenProvider provider;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.addFilterBefore(new CorsFilter(corsConfigurationSource()), SessionManagementFilter.class)
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/hello", "/login", "/register").permitAll()
.anyRequest().authenticated()
.and()
.apply(new JwtConfigurer(provider));
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config.applyPermitDefaultValues());
return source;
}
}
- Exception:
public class JwtAuthenticationException extends AuthenticationException {
public JwtAuthenticationException(String explanation) {
super(explanation);
}
}
4.Controller:
@PostMapping("/login")
@ResponseStatus(HttpStatus.OK)
public Properties login(@RequestBody AuthenticationInDto dto) {
try {
String username = dto.getUsername();
String password= dto.getPassword();
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
Optional<User> user = userService.findByUsername(username);
if (!user.isPresent()) {
throw new UsernameNotFoundException("User with username: " + username + " not found");
}
String token = provider.createToken(username, user.get().getRole());
Properties response = new Properties();
response.setProperty("jwtToken", token);
return response;
} catch (AuthenticationException e) {
throw new BadCredentialsException(e.getMessage());
}
}
Но, тем не менее, он не перехватывает мое исключение(не пробрасывает BadCredentialsException) - сервер падает с ошибкой 500, хоть у меня и есть обработчик:
@RestControllerAdvice
public class ControllersExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(BadCredentialsException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ErrorDto handleBadCredentials(BadCredentialsException ex) {
return new ErrorDto(HttpStatus.UNAUTHORIZED.value(),
ex.getMessage());
}
@ExceptionHandler(RegistrationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ErrorDto handleRegistrationException(RegistrationException ex) {
return new ErrorDto(HttpStatus.BAD_REQUEST.value(),
ex.getMessage());
}
}
Ответы (1 шт):
Я упростил до любой ошибки в фильтре:
@Component
public class TokenFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String token = servletRequest.getParameter("token");
if (token == null || !token.equals("123456")) {
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
httpServletResponse.sendError(HttpStatus.UNAUTHORIZED.value());
}
filterChain.doFilter(servletRequest, servletResponse);
}
}
В вашем случае поймать ошибку в фильтре и отправить sendError(). В sendError() вторым аргументом можно добавить описание ошибки.
Еще непонятно, каким образом у вас получилось скомпилировать, если в методе validateToken() исключение обрабатываемое, а интерфейс GenericFilterBean задает методу только 2 возможных исключения: IOException и ServletException.
А через @RestControllerAdvice поймать ошибку по идее не выйдет, т.к. servlet -> before filter -> spring mvc -> after filter.
Кстати через @Aspect вполне себе работает:
@Aspect
@Component
public class ExceptionHandlerAspect {
@Before(value = "within(@org.springframework.web.bind.annotation.RestController *)")
public void before(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String token = request.getParameter("token");
if (token == null || !token.equals("123456")) {
throw new BadCredentialsException(HttpStatus.UNAUTHORIZED, "bad token");
}
}
}