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

Quarkus Security-JWT中从外部加载groups声明遇阻塞线程问题求助

解决Quarkus Security-JWT中自定义Principal Factory的阻塞操作异常

你遇到的io.quarkus.runtime.BlockingOperationNotAllowedException是因为JWT解析流程默认在Vert.x IO线程执行,该线程禁止执行阻塞操作(如JDBC调用、同步HTTP请求)。即使添加了@Blocking注解,由于JWTCallerPrincipalFactory的执行上下文限制,线程切换逻辑可能未生效。以下是几种可行的解决方案:

方案1:确保@Blocking注解正确触发线程切换

如果坚持使用自定义JWTCallerPrincipalFactory,需确保方法在Quarkus工作线程执行,同时DAO层的阻塞操作也标记为@Blocking:

修改自定义Principal Factory

@ApplicationScoped
@Alternative
@Priority(1)
public class JwtPrincipalFactory extends JWTCallerPrincipalFactory {

    private final DefaultJWTTokenParser parser = new DefaultJWTTokenParser();
    private final UserRepository userRepository;

    // 构造注入阻塞式Panache仓库
    public JwtPrincipalFactory(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Blocking // 强制方法在Quarkus工作线程执行
    @Override
    public JWTCallerPrincipal parse(final String token, final JWTAuthContextInfo context) throws ParseException {
        final JwtContext jwtContext = parser.parse(token, context);
        final String type = ((JsonWebStructure) jwtContext.getJoseObjects().getFirst()).getHeader("typ");
        
        // 从JWT的sub声明获取用户ID
        String userId = jwtContext.getJwtClaims().getSubject();
        // 调用阻塞式DAO方法加载权限组
        Set<String> groups = userRepository.findGroupsByUserId(userId);
        
        jwtContext.getJwtClaims().setClaim("groups", groups);
        return new DefaultJWTCallerPrincipal(type, jwtContext.getJwtClaims());
    }
}

阻塞式Panache仓库实现

@ApplicationScoped
public class UserRepository extends PanacheRepository<User> {

    @Blocking // 标记此方法为阻塞操作,自动切换到工作线程
    public Set<String> findGroupsByUserId(String userId) {
        return find("userId", userId)
                .firstResultOptional()
                .map(User::getGroups)
                .orElse(Set.of("none"));
    }
}

方案2:使用Security Identity Augmentor(推荐)

Quarkus官方推荐用SecurityIdentityAugmentor扩展身份信息,它天然支持异步/Reactive操作,无需担心线程阻塞问题:

实现Identity Augmentor

@ApplicationScoped
public class GroupsIdentityAugmentor implements SecurityIdentityAugmentor {

    private final UserReactiveRepository userReactiveRepository;

    public GroupsIdentityAugmentor(UserReactiveRepository userReactiveRepository) {
        this.userReactiveRepository = userReactiveRepository;
    }

    @Override
    public Uni<SecurityIdentity> augment(SecurityIdentity identity, AuthenticationRequestContext context) {
        // 从已认证的身份中获取用户ID
        String userId = identity.getPrincipal().getName();
        // 异步加载用户权限组
        return userReactiveRepository.findGroupsByUserId(userId)
                .map(groups -> SecurityIdentity.builder(identity)
                        .addRoles(groups) // 将权限组添加为角色,直接用于端点权限控制
                        .build())
                .onFailure().recoverWithItem(identity); // 加载失败时返回原始身份
    }
}

Reactive Panache仓库实现

@ApplicationScoped
public class UserReactiveRepository extends PanacheRepositoryBase<User, Long> {

    public Uni<Set<String>> findGroupsByUserId(String userId) {
        return find("userId", userId)
                .firstResult()
                .map(User::getGroups)
                .onItem().ifNull().continueWith(Set.of("none"));
    }
}

这种方式无需自定义Principal Factory,直接扩展已认证的SecurityIdentity,权限组会自动被Quarkus Security用于端点的角色校验,完全适配Reactive编程模型。

方案3:全局切换JWT认证线程(不推荐)

通过配置强制JWT认证流程在工作线程执行,但会影响全局Reactive性能,仅作为临时方案:

quarkus.smallrye-jwt.auth-mechanism=BLOCKING

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:57:03