Reputation: 105
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.
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());
}
}
@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.
@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
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
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
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