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

Quarkus中基于Keycloak/OIDC的WebSocket安全配置求助

针对Quarkus WebSocket安全认证问题的解决方案

问题背景回顾

已通过Keycloak完成REST端点的Bearer令牌认证,客户端调用正常。现需为WebSocket端点实现同级别安全,配置WebSocketSecurityConfigurator后日志显示access_token已处理,但onOpen()/onMessage()抛出io.quarkus.runtime.BlockingOperationNotAllowedException,注入CurrentIdentityAssociation后onOpen()未执行。


1. 返回Uni的onOpen()执行问题

Quarkus WebSocket框架会自动订阅onOpen()返回的Uni,无需手动处理。如果onOpen()未执行,核心原因是认证环节阻塞或失败:

  • 检查WebSocketSecurityConfigurator实现,绝对不能在其中做同步阻塞操作(比如直接调用数据库、外部服务)——安全配置逻辑运行在IO线程,阻塞会直接导致连接被拒绝,onOpen()自然无法触发。
  • 必须把所有阻塞逻辑包装成Uni异步执行,示例:
@ApplicationScoped
public class CustomWebSocketSecurityConfigurator implements WebSocketSecurityConfigurator {
    @Inject
    TokenValidator tokenValidator;

    @Override
    public Uni<SecurityIdentity> configure(WebSocketSecurityRequest request) {
        String token = request.getHeader("Authorization").replace("Bearer ", "");
        // 异步校验令牌并构建SecurityIdentity
        return tokenValidator.validate(token)
                .map(validatedToken -> new DefaultSecurityIdentity.Builder()
                        .setPrincipal(validatedToken.getSubject())
                        .addRoles(validatedToken.getRoles())
                        .build());
    }
}
  • 若onOpen()返回Uni<Void>,确保内部逻辑全异步:
@OnOpen
public Uni<Void> onOpen(Session session) {
    return someAsyncService.init(session)
            .replaceWithVoid();
}

2. 访问JWT声明的两种方式

完全可以在WebSocket方法中获取JWT声明,推荐两种可靠方式:

  • 通过SecurityIdentity读取:注入SecurityIdentity,直接从主体或属性中获取声明(前提是WebSocketSecurityConfigurator已将声明存入SecurityIdentity):
@Inject
SecurityIdentity securityIdentity;

@OnMessage
public Uni<String> onMessage(String message) {
    String subject = securityIdentity.getPrincipal().getName();
    // 获取自定义声明,比如email
    String email = securityIdentity.getAttribute("email");
    return Uni.createFrom().item("Received from " + subject);
}
  • 存储完整JWT对象:在WebSocketSecurityConfigurator构建SecurityIdentity时,把解析后的JWT对象存入属性:
// 构建SecurityIdentity时添加
.addAttribute("jwt", validatedToken)

后续在端点方法中直接取出使用:

Jwt jwt = securityIdentity.getAttribute("jwt");
String customClaim = jwt.getClaim("custom_claim");

3. @RolesAllowed注解的有效性

@RolesAllowed在Quarkus WebSocket场景下完全有效,前提是:

  • WebSocketSecurityConfigurator返回的SecurityIdentity中已正确包含用户角色(比如从JWT的realm_access.roles字段提取);
  • 在WebSocket端点类或方法上正确添加注解,示例:
@ServerEndpoint("/ws/secured")
@RolesAllowed("admin")
public class SecuredWebSocketEndpoint {
    // ...
}

或方法级别控制:

@OnMessage
@RolesAllowed("user")
public Uni<String> handleUserMessage(String message) {
    // ...
}

既然同应用的REST接口@RolesAllowed正常工作,说明Keycloak角色配置无问题,只要WebSocket的SecurityIdentity角色与REST保持一致,注解即可生效。


额外排查建议

  • 查看日志中令牌校验的细节,比如过期、签名无效等问题会直接导致认证失败,onOpen()无法执行;
  • 确认客户端建立WebSocket连接时,Authorization请求头正确携带了Bearer令牌(前端示例:new WebSocket(url, { headers: { 'Authorization': 'Bearer ' + token } }));
  • 所有onOpen()/onMessage()中的耗时操作必须包装成Uni异步执行,避免触发BlockingOperationNotAllowedException。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 08:00:59