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

