I have a method:
#GetMapping("/foo")
public void> foo(JwtAuthenticationToken token) throws ExecutionException, InterruptedException {
Object object = ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication().getPrincipal())
.toFuture()
.get();
System.out.println(object);
JwtAuthenticationToken object which is method argument is succesfully autowired and not null but
result of
Object object = ReactiveSecurityContextHolder.getContext()
.map(securityContext -> securityContext.getAuthentication().getPrincipal())
.toFuture()
.get();
is null.
Could you please explain why ? Is there way to fix it ?
related topic: How to get jwt token value in spring webflux? (to exchange it with Minio STS token)
It is not necessary to use ReactiveSecurityContextHolder to get the Jwt instance. For example, if JwtAuthenticationToken is non-null, you can get the Jwt instance by doing:
public Mono<Void> foo(JwtAuthenticationToken token) throws ExecutionException, InterruptedException {
Jwt jwt = token.getToken();
// ...
}
Or, you can translate it with #AuthenticationPrincipal like so:
public Mono<Void> foo(#AuthenticationPrincipal Jwt jwt) throws ExecutionException, InterruptedException {
// ...
}
Further, it is not possible to use block() with ReactiveSecurityContextHolder in the way the OP describes. block() subscribes immediately, and there is nothing immediately available in the Reactor Context at this point.
The reactive stack works somewhat in the inverse to perhaps what you are thinking. While there is a filter in Spring Security called ReactorContextWebFilter that populates the Reactor Context with the SecurityContext, its work is deferred until the HTTP response is subscribed to, for example by the browser. block() at this point states (correctly) that the Reactor Context is empty. Instead, if you participate in the existing subscription (instead of calling block()), then you are also deferring your work in the same way and will be able to use ReactiveSecurityContextHolder.
EDIT: Having read the additional context about the OP's situation from How to get jwt token value in spring webflux? (to exchange it with Minio STS token), it's clear now that the OP is aware of these ways to get the instance of a Jwt, and does not want to use them. I'll leave the answer here for completeness anyway.
Not really sure how token is related to the result of the bucketExists and why do you convert it to the CompletableFuture but here is an example how you can get token from the context.
#GetMapping(value = "/someEndpoint")
public Mono<Boolean> foo() {
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> ctx.getAuthentication().getPrincipal())
.cast(Jwt.class)
.map(jwt -> useToken(jwt));
}
Here is a test using org.springframework.security:spring-security-test
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
class RouteConfigTest {
#Autowired
private WebTestClient client;
#Test
void test() {
this.client.mutateWith(mockJwt())
.get()
.uri("/someEndpoint")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk();
}
}
Related
I want to add some custom auth headers to my request, when the dev-mode is activated. This should make the developement easier for me, since I don't have to add them on my self manually.
What I have found is the method annotation ServerRequestFilter which intercepts an ongoing request before hitting the controller level. The annotated function provides ContainerRequestContext argument where I can add my headers easily.
Now the problem: To know the custom headers value, I have to make an external rest call (I'm using the RestClient for that) to an API. Since I'm using the reactive library I get the exception org.jboss.resteasy.reactive.common.core.BlockingNotAllowedException because of this call.
Since I'm using Kotlin, i tried to mark my method as suspendable. But this lead to a build error Method 'preCall$suspendImpl of class 'com.kaz.request.service.RequestInterceptor' cannot be static as it is annotated with '#org.jboss.resteasy.reactive.server.ServerRequestFilter'
Here my code:
#ApplicationScoped
class RequestInterceptor #Inject constructor(val authService: AuthService) {
#ServerRequestFilter
suspend fun preCall(requestContext: ContainerRequestContext) {
validateIdTokenHeader(requestContext)
}
private suspend fun validateIdTokenHeader(requestContext: ContainerRequestContext) {
val isTokenHeaderAbsent = requestContext.getHeaderString(Headers.X_ID_TOKEN) == null
val isDevModeEnabled = LaunchMode.current() == LaunchMode.DEVELOPMENT
if (isTokenHeaderAbsent && !isDevModeEnabled) {
throw AuthExceptions.ID_TOKEN_IS_ABSENT
} else {
injectDevUserIdToken(requestContext)
}
}
private suspend fun injectDevUserIdToken(requestContext: ContainerRequestContext) {
// This call is making the request and block
val idToken = authService.getIdToken("someHash")
requestContext.headers.putSingle(Headers.X_ID_TOKEN, idToken)
}
}
What I also tried to do is using Mutiny in my RestClient. I subscribed to the Uni and added the header when the result was available. But then I had the problem, that my controller/endpoint was already called before the header could be added to the request.
An endpoint could look like this:
#Path("hello/{id}")
#GET
suspend fun get(
//This header is what I want to add automatically, when dev mode is active.
#RestHeader(Headers.X_ID_TOKEN) idToken: UserIdToken,
#RestPath id: UUID,
#Context httpRequest: HttpServerRequest
): Response {
val request = RequestDTO(id, excludeFields, idToken.userId)
val envelope = service.findById(request)
return ResponseBuilder.build(httpRequest, envelope)
}
how about create a defer Uni and emit it on worker pool?
val idToken = Uni.createFrom().item { authService.getIdToken("someHash") }
.runSubscriptionOn(Infrastructure.getDefaultWorkerPool())
or call the blocking code with the default io coroutines dispatcher
withContext(Dispatchers.IO){
authService.getIdToken("someHash")
}
and finally, maybe use Vertx.executeBlocking will be the simplest way
vertx.executeBlocking(Uni.createFrom().item {
authService.getIdToken("someHash")
})
Something in your implementation is blocking IO. So you can try to find it and wrap it with Uni or you can mark the method with #Blocking annotation, then the filters will be run on the worker thread.
Sometimes it makes sense not just to read the online documentation page but also the inline comments of the according class.
ServerRequestFilter.java says:
The return type of the method must be either be of type void, Response, RestResponse, Optional<Response>, Optional<RestResponse>, Uni<Void>, Uni<Response> or Uni<RestResponse>.
...
Uni<Void> should be used when filtering needs to perform a
non-blocking operation but the filter cannot abort processing. Note
that Uni<Void> can easily be produced using: Uni.createFrom().nullItem()
So the entrypoint function has to return an Uni as well, not just the RestClient. Changing it like the following will be enough
#ServerRequestFilter
fun preCall(requestContext: ContainerRequestContext) : Uni<Void> {
//Here I return a Uni<Void> back using Uni.createFrom().nullItem()
return validateIdTokenHeader(requestContext)
}
I have a piece of code that calls an external service. And I wanted to map error from this service. I tried to do that this way:
public Mono<Void> patch(String userId, Payload payload) {
return Mono.just(payload)
.flatMap(it -> client.patch(userId, PatchRequest.buildRequest(payload, userId))
.onErrorMap(throwable -> GeneralActionException.ofFailedSetup()));
}
But when I mocked the client to return RuntimeException
Mockito.when(client.patch(any(), any())).thenThrow(new RuntimeException());
It turned out my test:
StepVerifier.create(sut.patch(userId, payload))
.verifyError(GeneralActionException.class);
failed, because the returned error was RuntimeException:
However when I change the code a little, just like that:
public Mono<Void> patch(String userId, Payload payload) {
return Mono.just(payload)
.flatMap(it -> client.patch(userId, PatchRequest.buildRequest(payload, userId)))
.onErrorMap(throwable -> GeneralActionException.ofFailedSetup());
}
It turned out the test succeeded. The question is why? Because I don't understand why it works differently in both cases and especially why in the first example when error mapping is inside flatMap it doesn't map it to GeneralException?
Ok, I solved it. I mocked client wrongly.
Instead of:
Mockito.when(client.patch(any(), any())).thenThrow(new RuntimeException());
it should be:
Mockito.when(client.patch(any(), any())).thenReturn(Mono.error(RuntimeException::new));
as the client returns Mono<Void>
I'm trying to cache an authentication token response which is returned by a webclient call.
public Mono<Token> getToken() {
return webclient.post().retrieve()
.bodyToMono(Token.class)
.cache(this::getTokenLiveDuration, t -> Duration.ZERO, () -> Duration.ZERO).log();
}
public Mono<MyResponse> execute() {
return getToken().flatMap(token -> {
webclient.post().header("Auth", token.getValue())
.retrieve()
.bodyToMono(MyResponse.class)
}
}
But if I run execute() two time in a same instance, they have different tokens, which means the cache did not work.
What's the correct way of using .cache or the correct way to cache webclient response?
That's because each time getToken is called a new Mono is created with its own cache.
One way to make caching effective is creating a field for the cached token Mono and use that Mono in the execute method.
I'm adding cookies in gateway response using reactive global filters as:
chain.filter(exchange).then(<a mono relevant to response>)
When I'm trying to test this using spock then method is not invoked from the stubbed Mono.
The filter itself:
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange)
.then(refreshCookiesMono(exchange));
}
private Mono<Void> refreshCookiesMono(ServerWebExchange exchange) {
return Mono.fromRunnable(() -> {
//interactions with *exchange* and *chain*
});
}
This test passes despite 0 * _ in the end:
#Subject
CookieFilter cookieFilter = new CookieFilter(cookieHelper)
...
ServerWebExchange exchange = Mock ServerWebExchange
GatewayFilterChain chain = Mock GatewayFilterChain
Mono<Void> mono = Mock Mono
...
def "cookieFilter refreshes the cookie with a new value"() {
given:
when:
cookieFilter.filter(exchange, chain)
then:
1 * chain.filter(exchange) >> mono
0 * _
}
But in the code I invoke .then from the mono returned from .filter method.
Why isn't mono.then() taken into account? Of course when I try to test all the underlying logic - spock doesn't find interactions.
chain.filter(exchange) returns the instance of mono that you've mocked.
You haven't specified any expectations on that mock (and this is the answer to your question, I believe), so the test doesn't really check the Filter, it only checks that there was one call to chain.filter(exchange).
Besides, Spock supports Stubs in additions to Mocks, and unlike many other frameworks there is a difference between those:
Mocks are "heavier" and you can do verification(s) on them (in "then" block), stubs are much more "lightweight" and you usually can specify expectations on them in a "given" block.
Usually you use Mocks if you want to mock some interaction and base the test on the protocol that gets managed around this interaction, in other cases Stubs are preferable.
Losing hope to test the filter end-to-end I've extracted my runnable in a separate package private method and tested it without Monos and any other reactive things.
The code in my filter now looks like:
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange)
.then(Mono.fromRunnable(refreshCookies(exchange)));
}
Runnable refreshCookies(ServerWebExchange exchange) {
return () -> {
//magic happens here ....
};
}
Any further clues and refactoring proposals are appreciated.
I am trying to make a reactive #WebFilter that executes stuff before and after the actual server exchange (i. e. the controller code handling the request):
public static class Foobar implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono.empty()
.doOnEach(s -> /* BEFORE */))
.then(chain.filter(exchange) /* CONTROLLER */)
.and(Mono.empty().doOnEach(s -> /* AFTER */))
.and(Mono.empty().doFinally(s -> /* FINALLY */));
}
}
Everything works as expected for simple GET requests that return a Mono:
#RestController
#RequestMapping
public static class Foo {
#GetMapping
#PostMapping(value = "foo")
public Mono<String> foo(ServerWebExchange exchange) {
return Mono.just("FOOBAR").map(e -> "OK");
}
}
But something really unexpected happens when the controller receives a parameter annotated as #RequestBody. Say, for example a POST request that takes a Mono<String> from the client:
#RestController
#RequestMapping
public static class Bar {
#PostMapping(value = "bar")
public Mono<String> bar(ServerWebExchange exchange, #RequestBody Mono<String> text) {
return text.map(s -> "OK");
}
}
In this case, all steps in my filter are executed before the controller gets to complete the request. This means that the web exchange is committed independently of the filter and therefore I cannot do anything right after the response is sent back to the client.
So I'm wondering:
Is this some kind of Spring bug?
Am I doing something wrong?
Or is this simply the expected behavior?
I've created a small Gist containing a test case that reproduces the problem:
https://gist.github.com/guillermocalvo/740b4fcab471ebc6fe69227fee6d79d5
Edit after Brian's comment:
I still think this might be a bug because Mono.then doesn't seem to have any effect at all:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
exchange.getResponse().isCommitted()))
.then().doOnEach(s -> logger.info("then doOnEach response committed:" +
exchange.getResponse().isCommitted()))
.doFinally(s -> logger.info("doFinally response committed:" +
exchange.getResponse().isCommitted()));
}
Additionally, if I put stuff in doOnEach is not executed either:
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("FILTER-BEFORE-CHAIN/commited=" +
response.isCommitted()))
.doOnEach(s -> logger.info("FILTER-AFTER-CHAIN/commited=" +
response.isCommitted()))
.doFinally(s -> logger.info("FILTER-FINALLY/commited=" +
response.isCommitted()));
}
I don't think this is a bug in Spring (nor in Reactor in this case), but rather a wrong choice of operators to achieve what you're trying to do.
Mono.doOnEach is executed for each signal (next element, completion, error, cancel); this will be executed several times per request in our case.
Mono.and joins the termination signals - so it waits for both Mono to be done and then completes. But both Monos are not executed sequentially, they're subscribed at the same time. Mono.just completes right away, independently of what happens with the filter chain.
In your case, you don't need to have something more complex than adding one operator when the processing starts (doOnSubscribe happens when the mapping has been done and we're starting to execute the handler) and another one when we're done (doFinally happens when it's done, with completion, error or cancellation).
#Component
public class MyFilter implements WebFilter {
Logger logger = LoggerFactory.getLogger(getClass());
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.doOnSubscribe(s -> logger.info("onSubscribe response committed:" +
exchange.getResponse().isCommitted()))
.doFinally(s -> logger.info("doFinally response committed:" +
exchange.getResponse().isCommitted()));
}
}
Note that this approach works as long as you're not performing blocking operations in the DoOnXYZ methods, as they're side effects methods. Doing so will create significant performance problems in your application (and reactor might even reject that operation with an exception). So logging, adding an element to a map are fine - but publishing something to an event queue, for example, is not. In this case, you should use instead chain operations with Mono.then().
Edit
I don't think there is a bug with Mono.then() - doOnEach only works with signals sent downstream (next, error, complete). In this case, you should only get the complete signal. If you'd like to get the context in all cases, you can take a look at Mono.deferWithContext.