You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebFlux配置MinioClient时JWT获取阻塞问题求解

问题场景

我有一个基于Spring WebFlux编写REST服务的Spring Boot应用,此前通过用户名/密码认证访问Minio,运行正常。现在希望通过应用的JWT令牌换取Minio STS令牌,已实现测试方法并验证可行:

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

该测试代码可成功返回true(因为mybucket确实存在),但这只是临时测试逻辑。我需要将minioClient移至配置类中作为Bean管理,但配置中需要动态获取当前请求的JWT令牌来构建凭证提供者,于是编写了如下配置:

@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) {
                            // 此处抛出异常
                            System.out.println(e);
                        }

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

但运行时block()方法抛出异常:

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

尝试用.toFuture().get()替代block(),但返回null,求解决办法。

解决方案

核心问题是在非阻塞的WebFlux线程中调用阻塞方法(block()/get())会破坏响应式模型,且配置类初始化时ReactiveSecurityContextHolder中并没有当前请求的上下文(因为Bean是单例,初始化阶段无请求上下文)。以下两种方案可解决问题:

1. 动态构建请求级别的MinioClient

既然令牌是请求级别的,可创建一个MinioClientProvider组件,在每个请求中动态获取当前JWT并构建MinioClient,完全遵循响应式非阻塞模型:

@Component
public class MinioClientProvider {

    @Value("${minio.region}")
    private String region;

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.role-arn}")
    private String roleArn;

    public Mono<MinioClient> getMinioClient() {
        return ReactiveSecurityContextHolder.getContext()
                .map(SecurityContext::getAuthentication)
                .cast(JwtAuthenticationToken.class)
                .map(JwtAuthenticationToken::getToken)
                .map(jwt -> {
                    WebIdentityProvider provider = new WebIdentityProvider(
                            () -> new Jwt(jwt.getTokenValue(), 1000),
                            roleArn,
                            null, null, null, null, null
                    );
                    return MinioClient.builder()
                            .region(region)
                            .endpoint(endpoint)
                            .credentialsProvider(provider)
                            .build();
                });
    }
}

业务代码中使用示例:

@RestController
public class MinioController {

    private final MinioClientProvider clientProvider;

    public MinioController(MinioClientProvider clientProvider) {
        this.clientProvider = clientProvider;
    }

    @PostMapping("/check-bucket")
    public Mono<Boolean> checkBucket() {
        return clientProvider.getMinioClient()
                .flatMap(client -> Mono.fromCallable(() -> 
                        client.bucketExists("mybucket").build()
                ))
                .onErrorReturn(false);
    }
}

2. 自定义适配响应式上下文的凭证提供者

如果希望保留MinioClient的单例特性,可自定义一个适配类,利用DeferredCredentialsProvider延迟获取凭证(仅在Minio实际发起请求时触发):

public class ReactiveWebIdentityProvider implements CredentialsProvider {

    private final String roleArn;
    private final Supplier<Mono<Jwt>> jwtSupplier;

    public ReactiveWebIdentityProvider(String roleArn, Supplier<Mono<Jwt>> jwtSupplier) {
        this.roleArn = roleArn;
        this.jwtSupplier = jwtSupplier;
    }

    @Override
    public Credentials retrieve() throws IOException {
        try {
            // 仅在Minio需要凭证时调用block,此时已处于请求上下文线程中
            Jwt jwt = jwtSupplier.get().block();
            if (jwt == null) {
                throw new IOException("无法从安全上下文获取JWT令牌");
            }
            WebIdentityProvider delegate = new WebIdentityProvider(
                    () -> jwt,
                    roleArn,
                    null, null, null, null, null
            );
            return delegate.retrieve();
        } catch (Exception e) {
            throw new IOException("获取STS凭证失败", e);
        }
    }
}

配置类中注册单例Bean:

@Bean
public MinioClient minioClient(
        @Value("${minio.region}") String region,
        @Value("${minio.endpoint}") String endpoint,
        @Value("${minio.role-arn}") String roleArn) {

    Supplier<Mono<Jwt>> jwtSupplier = () -> ReactiveSecurityContextHolder.getContext()
            .map(SecurityContext::getAuthentication)
            .cast(JwtAuthenticationToken.class)
            .map(JwtAuthenticationToken::getToken)
            .map(jwt -> new Jwt(jwt.getTokenValue(), 1000));

    return MinioClient.builder()
            .region(region)
            .endpoint(endpoint)
            .credentialsProvider(new ReactiveWebIdentityProvider(roleArn, jwtSupplier))
            .build();
}

内容的提问来源于stack exchange,提问作者gstackoverflow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 21:45:37