The examples on the spring docs related to HandlerFilterFunction are reading headers or queryParams or path. Is there an example of reading request body and do validation? On success, pass on the request to next handler.

webflux-fn-handler-filter-function

Following I have tried so far

Usecase

Use case is to validate data in JWT with request body. JWT sub contains

{
  "sub": "{\"data\": {\"street\": \"Street 1\", \"landmark\": \"TallestTower\"}}"
}

Request must contain exact data. Forbidden if not match, otherwise pass on for further processing.

Using:

  • Springboot 3,
  • WebFlux reactive with router functions
  • Jose4j for JWT.

Behavior

During debug, when it reaches to ResourceRoutesHandler.create() function, and tries to execute request.bodyToMono().flatMap(), it just exits with HTTP 200. There are several further processes in .flatMap() but they are not executed. Interestingly, there is no ServerResponse.ok() in the workspace to produce such result with HTTP 200 (without body).

My understanding this ServerRequest.from(request).build() is causing it.

This question already posted in Stackoverflow. Read request body in HandlerFilterFunction and pass to next handler if valid

Is there a way to validate request body and send the same request or within the same subscription? Or is there an alternate way to validate request data before reaches to route handler?

Code

Routes


@Configuration
@RequiredArgsConstructor
public class ResourceRoutes {

  final JwtHandlerFilterFunction jwtHandlerFilterFunction;

  public RouterFunction<ServerResponse> createResource(ResourceRoutesHandler recourceRoutesHandler) {
    return RouterFunction.route(
            POST("/create").and(accept(MediaType.APPLICATION_JSON)),
            recourceRoutesHandler::create
        )
        .filter(jwtHandlerFilterFunction);
  }
}

HandlerFilterFunction


@Component()
@RequiredArgsConstructor
public class JwtHandlerFilterFunction
    implements HandlerFilterFunction<ServerResponse, ServerResponse> {

  final ObjectMapper objectMapper;
  final JwtValidateService jwtValidateService;

  @Override
  public Mono<ServerResponse> filter(final ServerRequest request,
                                     final HandlerFunction<ServerResponse> next) {

    Headers headers = request.headers();

    Optional<String> jwtToken = Optional.ofNullable(headers.firstHeader("Auth-Token"));

    return jwtToken.map(_jwt -> jwtValidateService
                       .validateReactive(_jwt)
                       .flatMap(verifyPayload(request))
                       .flatMap(validMessage -> next.handle(
                        // clone here and pass on to next
                        // this seems causing fail to execute request in route handler
                           ServerRequest.from(request)
                                        .build()
                       ))
                       .onErrorResume(throwable -> ServerResponse.status(HttpStatus.FORBIDDEN)
                           .contentType(MediaType.APPLICATION_JSON)
                           .bodyValue("Authorization failed")
                       ) // jwt validation failed
                   )
                   .orElse(next.handle(request)); // to next if header not present
  }

  private Function<String, Mono<String>> verifyPayload(final ServerRequest clientRequest) {
    return subject -> clientRequest
        .bodyToMono(String.class) // as the body already read how to pass on the ServerRequest to next handler?
        .flatMap(requestData -> Mono.fromCallable(() -> {
          // for simplicity
          if (!subject.equals(requestData)) {
            throw new RuntimeException("Invalid subject data");
          }
          return "Valid";
        }));
  }

}

Thank you for your time

Comment From: simonbasle

Answered on StackOverflow. We might internally discuss adding some guidance/warning about processing the body in the filter's javadoc.