1
votes

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.

2

2 Answers

6
votes

+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.

2
votes

You can use the Spring AuthenticationWebFilter instead of creating a new one. Take a look at this question on how to use it.

Set your own authenticationConverter to extract the credentials from the header and then you can implement your own AuthenticationEntryPoint and set it on the filter to send a custom response to the client.

You can take a look at the default implementation for the http basic authentication on how to achieve that.