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.