Skip to content
Advertisement

Throw an exception in a WebFilter class and handle it in a ControllerAdvice class

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.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement