I’m trying to implement a WebFilter
that checks a JWT and throw an exception if the check fails or the result is not valid. And I’ve a @ControllerAdvice
class that handles those exceptions. But it doesn’t work.
This is the WebFilter
class:
@Component public class OktaAccessTokenFilter implements WebFilter { private JwtVerifier jwtVerifier; @Autowired public OktaAccessTokenFilter(JwtVerifier jwtVerifier) { this.jwtVerifier = jwtVerifier; } @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { return Optional.ofNullable(exchange.getRequest().getHeaders().get("Authorization")) .flatMap(list -> list.stream().findFirst()) .filter(authHeader -> !authHeader.isEmpty() && authHeader.startsWith("Bearer ")) .map(authHeader -> authHeader.replaceFirst("^Bearer", "")) .map(jwtString -> { try { jwtVerifier.decodeAccessToken(jwtString); } catch (JoseException e) { throw new DecodeAccessTokenException(); } return chain.filter(exchange); }).orElseThrow(() -> new AuthorizationException()); } }
And the exception handler:
@ControllerAdvice public class SecurityExceptionHandler { @ExceptionHandler(AuthorizationException.class) public ResponseEntity authorizationExceptionHandler(AuthorizationException ex) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } @ExceptionHandler(DecodeAccessTokenException.class) public ResponseEntity decodeAccessTokenExceptionHandler(DecodeAccessTokenException ex) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } }
I think, the @ControllerAdvice
class can not handle exceptions that WebFilter
throws. Because, if I move the exceptions to the controller, it works.
I’ve change the code for this (for now):
@Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { Optional<String> authJwt = Optional.ofNullable(exchange.getRequest().getHeaders().get("Authorization")) .flatMap(list -> list.stream().findFirst()) .filter(authHeader -> !authHeader.isEmpty() && authHeader.startsWith("Bearer ")) .map(authHeader -> authHeader.replaceFirst("^Bearer", "")); if (authJwt.isPresent()) { String jwtString = authJwt.get(); try { jwtVerifier.decodeAccessToken(jwtString); } catch (JoseException e) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().writeWith(Mono.empty()); } } else { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().writeWith(Mono.empty()); } return chain.filter(exchange); }
What do you think about the problem? Do you know another way to implement it?
Advertisement
Answer
You might try defining an @Order()
for both your @ControllerAdvice
and @WebFilter
beans, and giving @ControllerAdvice
higher precedence.
However, I don’t think that’s the way to go, main reason being the @ControllerAdvice
exception handlers don’t return reactive types. Instead, I would define a bean which implements ErrorWebExceptionHandler
instead. This handler is added to reactive flow by `spring-webflux, so you don’t need to worry about the precedence. See this answer for details.