Reputation: 7394
Im building a React SPA and would like to interact with the back end using JSON. When an authentication fail I would like to be able to send a custom error message in the form of JSON. However given the code below:
.authenticationFailureHandler(((exchange, e) -> {
return Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getExchange().getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBuffer buf = exchange.getExchange().getResponse().bufferFactory().wrap("{\"test\":\"tests\"}".getBytes(StandardCharsets.UTF_8));
response.writeWith(Mono.just(buf));
});
})
Im getting the follwing error:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1 ; mode=block
Referrer-Policy: no-referrer
content-length: 0
<Response body is empty>
Response code: 200 (OK); Time: 1162ms; Content length: 0 bytes
However If I change response code it gets reflected in the response so I know the code is executed but no response body is returned.
What do I need to change to be able to send a response body back when authentication fails?
Upvotes: 0
Views: 1970
Reputation: 1401
You can also achieve this with a custom WebExceptionHandler and ErrorAttributes
.authenticationFailureHandler(webFilterExchange, exception) ->
customErrorWebExceptionHandler.handle(webFilterExchange.getExchange(), exception);
@Component
public class CustomErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
public CustomErrorWebExceptionHandler(
final CustomErrorAttributes customAttributes,
final ResourceProperties resourceProperties,
final ObjectProvider<List<ViewResolver>> viewResolversProvider,
final ServerCodecConfigurer serverCodecConfigurer,
final ApplicationContext applicationContext
) {
super(customAttributes, resourceProperties, applicationContext);
this.setViewResolvers(viewResolversProvider.getIfAvailable(Collections::emptyList));
this.setMessageWriters(serverCodecConfigurer.getWriters());
this.setMessageReaders(serverCodecConfigurer.getReaders());
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(final ErrorAttributes errorAttributes) {
if (errorAttributes instanceof CustomErrorAttributes) {
return RouterFunctions.route(RequestPredicates.all(),
(request) -> handleProblemDetail(request, (CustomErrorAttributes) errorAttributes)
);
}
throw new UnsupportedOperationException(errorAttributes.getClass().getName());
}
private Mono<ServerResponse> handleProblemDetail(final ServerRequest request, final CustomErrorAttributes error) {
final Map<String, Object> errorAttributes = error.getErrorAttributes(request, false);
return ServerResponse.status(Integer.parseInt(errorAttributes
.getOrDefault("status", "500").toString()))
.contentType(MediaType.APPLICATION_PROBLEM_JSON)
.body(Mono.just(errorAttributes), Map.class)
;
}
}
@Component
public class CustomErrorAttributes implements ErrorAttributes {
private static final String ERROR_ATTRIBUTE = CustomErrorAttributes.class.getName() + ".ERROR";
@Override
public Map<String, Object> getErrorAttributes(final ServerRequest request, final boolean includeStackTrace) {
final Throwable error = this.getError(request);
return somethingThatConvertsTheErrorToAMap(error);
}
@Override
public Throwable getError(final ServerRequest request) {
return (Throwable)request.attribute(ERROR_ATTRIBUTE).orElseThrow(() -> {
return new IllegalStateException("Missing exception attribute in ServerWebExchange");
});
}
@Override
public void storeErrorInformation(final Throwable error, final ServerWebExchange exchange) {
exchange.getAttributes().putIfAbsent(ERROR_ATTRIBUTE, error);
}
}
Upvotes: 0
Reputation: 59221
I think this could work:
.authenticationFailureHandler(((exchange, e) -> {
ServerHttpResponse response = exchange.getExchange().getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
DataBuffer buf = exchange.getExchange().getResponse().bufferFactory().wrap("{\"test\":\"tests\"}".getBytes(StandardCharsets.UTF_8));
return response.writeWith(Mono.just(buf));
});
})
There might be a better way than writing JSON as a String, but I believe this should work.
Your attempt was not working because nothing was subscribing to the returned Publisher
by response.writeWith(Mono.just(buf))
. Since Publishers are lazy, nothing would get written to the response.
Upvotes: 1