Search code examples
springspring-bootspring-webfluxspring-web

Unable to send custom body in Webfilter if Authorization header doesn't exist


I am trying to intercept all the requests in my Spring Boot Webflux application (Spring boot 2.0.0.M7) using Webfilter and check for the existence of "Authorization" header. If not present I want to stop the request processing and send out custom HttpStatus and also custom body. Custom HttpStatus is working but I am not able to write custom message to HTTP body. Below

import java.time.LocalDateTime;

import org.apache.commons.lang.SerializationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;

public class RequestContextFilter implements WebFilter{

    Logger LOG = LoggerFactory.getLogger(RequestContextFilter.class);

    @Autowired
    private WebClient.Builder webclientBuilder;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        LOG.debug("Inside RequestContextFilter.."+ exchange);
        String authorizationHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        if(authorizationHeader == null) {
            exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
            ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST);
            apiError.setMessage("Missing Authorization Header");
            apiError.setTimestamp(LocalDateTime.now());
   // The below code of writing to body is NOT WORKING
            exchange.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(SerializationUtils.serialize(apiError))));
            return Mono.empty();
        }
        return chain.filter(exchange);

    }


}

ApiError.java class is nothing but my custom object I want to include in the response.

public class ApiError  implements Serializable{

       private HttpStatus status;
       @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
       private LocalDateTime timestamp;
       private String message;
       private String debugMessage;
       private List<ApiSubError> subErrors;


       public ApiError() {
           timestamp = LocalDateTime.now();
       }

       public ApiError(HttpStatus status) {
           this();
           this.status = status;
       }

       public ApiError(HttpStatus status, Throwable ex) {
           this();
           this.status = status;
           this.message = "Unexpected error";
           this.debugMessage = ex.getLocalizedMessage();
       }

       public ApiError(HttpStatus status, String message, Throwable ex) {
           this();
           this.status = status;
           this.message = message;
           this.debugMessage = ex.getLocalizedMessage();
       }





}

I curl the endpoint and this Webfilter does work and it sends the right HttpStatus code 400 but doesn't have the ApiError.

Request (No Authorization header):

curl -X GET "http://localhost:8080/accounts"--verbose

Response:

HTTP/1.1 400 Bad Request
content-length: 0

The status works and the filter is being invoked but no object response. I did try to write the bytes after converting to JSON using jackson the raw bytes to DataBufferFactory but it doesn't work.


Solution

  • +1 to what @bsamartins said.

    Now about your particular solution: the writeWith method returns a Publisher. If nothing subscribes to it, then nothing happens. You should replace

    exchange.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(SerializationUtils.serialize(apiError))));
    return Mono.empty();
    

    with

    return exchange.getResponse()
                   .writeWith(Mono.just(new DefaultDataBufferFactory().wrap(SerializationUtils.serialize(apiError))));
    

    With that change, Spring WebFlux will subscribe to the returned Publisher and your code will write to the response.