user3139545
user3139545

Reputation: 7394

Unable to return JSON data with Webflux authenticationFailureHandler

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

Answers (2)

McGin
McGin

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

Brian Clozel
Brian Clozel

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

Related Questions