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
相关产品推荐
相关产品推荐

