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

gstackoverflow
  • 36,709
  • 117
  • 359
  • 710
  • 3
    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. – Numichi Dec 21 '22 at 12:29
  • Where does JWT come from? Does the application create it? Is it created after logging in? – Elyorbek Ibrokhimov Dec 30 '22 at 05:51
  • @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 ReactiveSecurityContextHolder – gstackoverflow Dec 30 '22 at 10:50
  • 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? – Elyorbek Ibrokhimov Dec 30 '22 at 12:08
  • 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 ? – gstackoverflow Dec 30 '22 at 14:34
  • 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‘ https://projectreactor.io/docs/core/release/reference/#faq.wrap-blocking – Alex Jan 02 '23 at 17:08
  • @Alex Could you please provide the whole answer ? What is expected to do with blockingWrapper ? Will `blockingWrapper.block()` throw exception ? – gstackoverflow Jan 04 '23 at 08:42

1 Answers1

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);
                    }
                });
    }
}


  • 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 time – gstackoverflow Dec 22 '22 at 06:58
  • 1
    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 enoguh – Elyorbek Ibrokhimov Dec 22 '22 at 08:01
  • Than you. it works and `@Scope(BeanDefinition.SCOPE_PROTOTYPE)` is redundant – gstackoverflow Dec 22 '22 at 14:25
  • But unfortunately every time we call `@GetMapping("/minio")` - new MinioClient is created but I would like to avoid it. – gstackoverflow Dec 25 '22 at 15:09
  • 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. – Serg Vasylchak Dec 27 '22 at 12:54
  • @Serg Vasylchak Could you please provide the whole answer ? – gstackoverflow Dec 28 '22 at 10:34
  • I also found the similar question here: https://stackoverflow.com/questions/70701792/how-do-i-extract-information-from-jwt-which-is-stored-in-reactivesecuritycontext – gstackoverflow Jan 09 '23 at 11:34
  • @Serg Vasylchak I don't want to have single minioClient per user but single minioClient per application instance! – gstackoverflow Jan 10 '23 at 13:36