How to get jwt token value in spring webflux? (to exchange it with Minio STS token)
Asked Answered
B

1

9

I have sping-boot application with rest services written using Spring web flux.

For now I access minio using login/password authorizaton and it works fine.

For now I want to exchange application JWT token with STS minio token and I implemented method to test:

@PostMapping
public boolean test(JwtAuthenticationToken token) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
    MinioClient minioClient =
            MinioClient.builder()
                    .region(...)
                    .endpoint(...)              
                    .credentialsProvider(new WebIdentityProvider(
                           
                            () -> new Jwt(token.getToken().getTokenValue(), 1000),
                            String.valueOf(...),
                            null,
                            null,
                            null,
                            null,
                            null))
                    .build();
    return minioClient.bucketExists("mybucket").build());
}

This code successfully works and returns true because mybucket actually exists.

But it is only test and I need to move minioClient to the configuration. The issue here that I have to have credentials provider there.

So I've created folowing configuration:

@Bean
public MinioClient minioClient() {
    return MinioClient.builder()
            .region(...)
            .endpoint(...)
            .credentialsProvider(new WebIdentityProvider(
                   
                    () -> {
                        String block = null;
                        try {
                            block = ReactiveSecurityContextHolder
                                .getContext()
                                .map(context -> {
                                            return context
                                                    .getAuthentication()
                                                    .getPrincipal();

                                        }
                                )
                                .cast(Jwt.class)
                                .map(Jwt::token)
                                .block();
                        } catch (Exception e) {
                            // it fails here     <=======
                            System.out.println(e);
                        }

                        Jwt jwt = new Jwt(String.valueOf(block),
                                1000);
                        return jwt; },
                    String.valueOf(...),
                    null,
                    null,
                    null,
                    null,
                    null))
            .build();
}

But unfortunately method block() fails with exception:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-6 

Any ideas how to fix it?

P.S. _

I tried

.toFuture()
.get();

instead of .block();

but it returns null

Backwoodsman answered 21/12, 2022 at 11:0 Comment(7)
You must not use any blocker method in the reactive environment. If you use some Mono or Flux you have to stay in the reactor context because they will be run when somebody subscribes to it. Now I can think of 3 solutions. I can answer more after work If no one else answers by then.Therapist
Where does JWT come from? Does the application create it? Is it created after logging in?Marketa
@Elyorbek Ibrokhimov JWT comes from Keycloak server. This JWT created before application endpoint call and as I mentioned argument of public boolean test(JwtAuthenticationToken token) is not null and I expected to have the same jwt token in ReactiveSecurityContextHolderBackwoodsman
What is still not clear to me is that you are trying to achieve something in a stateless way but security context is stateful. Token can expire, payload may change and etc. If you have fixed JWT token value, why not then just store it in a properties file and read it from there while creating a bean definition?Marketa
JWT token is fixed within http request only. Different users can call our server with different JWT tokens. "are trying to achieve something in a stateless way" - could you please clarify this your phrase ?Backwoodsman
You don’t really need ‘CompletableFuture‘. Blocking calls are not allowed on “reactive” schedulers and in case your code is blocking you need to execute in on another scheduler - ‘boundedElastic‘ projectreactor.io/docs/core/release/reference/…Mozellamozelle
@Mozellamozelle Could you please provide the whole answer ? What is expected to do with blockingWrapper ? Will blockingWrapper.block() throw exception ?Backwoodsman
M
9

As Numichi stated in the comment you have to stay in the reactor context. One option is to create a bean of type Mono<MinioClient>.

    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public Mono<MinioClient> reactiveMinio() {
        return ReactiveSecurityContextHolder.getContext()
                .map(securityContext ->
                        (Jwt)securityContext.getAuthentication().getPrincipal())
                .map(jwt -> MinioClient.builder()
                        .region("someRegion")
                        .endpoint("someEndpoint")
                        .credentialsProvider(webIdentityProvider(jwt.token()))
                        .build());
    }

    private WebIdentityProvider webIdentityProvider(String token) {
        return new WebIdentityProvider(() -> new Jwt(token, 1000),
                "stsEndpoint",
                null,
                null,
                null,
                null,
                null);
    }

I think bean scope should be prototype since MinioClient is bound to security context.

Here is the sample usage of reactive MinioClient:


@RestController
public class MinioTest {

    private Mono<MinioClient> minioClient;

    public MinioTest(Mono<MinioClient> minioClient) {
        this.minioClient = minioClient;
    }

    @GetMapping("/minio")
    public Mono<Object> client() {
        return minioClient
                .map(minio -> {
                    try {
                        return minio.bucketExists(BucketExistsArgs
                                .builder()
                                .bucket("my-bucketname")
                                .build());
                    } catch (Exception e) {
                        return new Exception(e);
                    }
                });
    }
}


Marketa answered 22/12, 2022 at 3:53 Comment(8)
I will try this option to check if it works at all... looks really interesting but I am not sure that it is a good idea to create minioClient every timeBackwoodsman
I don't have experience with minio. However, my understanding is ReactiveSecurityContextHolder is stateful that is, it holds the authenticated user info. If authentication the same for every request then singleton scope could be enoguhMarketa
Than you. it works and @Scope(BeanDefinition.SCOPE_PROTOTYPE) is redundantBackwoodsman
But unfortunately every time we call @GetMapping("/minio") - new MinioClient is created but I would like to avoid it.Backwoodsman
It is, because you use jwt tokens, which are stateless by design - meaning you get a new security context with each request and always create a new minioClient. If I really needed to remain within same minioClient per user, I would create a bean with a Map<?, MinioClient> and access it in minioClient bean .map(principal -> minioClients.getOrDefault(principal.identifier(), createMinioClient()). This doesn't look really fancy, though, and should cover the problems like concurrent modification or jwt expiration, but it would keep the minio clients stateful.Weisshorn
@Serg Vasylchak Could you please provide the whole answer ?Backwoodsman
I also found the similar question here: #70702292Backwoodsman
@Serg Vasylchak I don't want to have single minioClient per user but single minioClient per application instance!Backwoodsman

© 2022 - 2024 — McMap. All rights reserved.