Miguel Fernández
Miguel Fernández

Reputation: 105

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?

Upvotes: 3

Views: 5943

Answers (3)

thomas Miller
thomas Miller

Reputation: 31

I had the same issue in webflux, you do not want to throw a direct exception or return a mono error in the webfilter), however you want to set the response to be the mono error containing the exception (Close to what you have done). This will allow the exception to be thrown in the correct part of the requests workflow and hence it will bubble up to your rest Controller advice

public class YourFilter implements WebFilter{

    @Override
    public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain){
        return exchange.getResponse().writeWith(Mono.error(new YouException()));
    }
}

Upvotes: 2

user3648235
user3648235

Reputation: 199

I have tried that and I have a CORS error Here is the error:

Access to XMLHttpRequest at 'http://localhost:8084/users/files' from origin 'http://localhost:4200' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Upvotes: 0

MuratOzkan
MuratOzkan

Reputation: 2730

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.

Upvotes: 5

Related Questions