Search code examples
javaspring-bootspring-securityspring-webfluxminio

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


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


Solution

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