使用ChannelInterceptor做JWT认证时,如何在SessionConnectedEvent中获取已认证用户的Principal
使用ChannelInterceptor做JWT认证时,如何在SessionConnectedEvent中获取已认证用户的Principal
我来帮你解决这个问题,核心原因是你之前的ChannelInterceptor里虽然完成了JWT认证,但没有把认证后的用户信息绑定到WebSocket会话中,导致SessionConnectedEvent监听器里拿不到Principal。下面是具体的解决步骤:
第一步:修正ChannelInterceptor的认证逻辑,绑定用户信息到WebSocket会话
在你的handleConnect方法中,认证成功后需要把Authentication对象绑定到StompHeaderAccessor,并且重新构建消息(因为原消息的Header是不可变的)。修改后的代码如下:
private Message<?> handleConnect(Message<?> message, StompHeaderAccessor accessor) { String authorizationHeader = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION); if (authorizationHeader == null || !authorizationHeader.startsWith(JwtService.BEARER_PREFIX)) { throw new MessageHandlingException(message, "Missing or invalid authorization header"); } String token = authorizationHeader.substring(JwtService.BEARER_PREFIX.length()).trim(); try { var jwtAuthToken = new JwtAuthenticationToken(token); // 执行认证,获取已认证的Authentication对象 Authentication authenticatedAuth = authManager.authenticate(jwtAuthToken); // 关键:将认证后的用户对象绑定到StompHeaderAccessor accessor.setUser(authenticatedAuth); // 必须设置为可变,否则Header的修改不会生效 accessor.setLeaveMutable(true); // 重新构建Message,使用修改后的Header信息 return MessageBuilder.createMessage(message.getPayload(), accessor.getMessageHeaders()); } catch (BadCredentialsException e) { throw new MessageHandlingException(message, e.getMessage()); } catch (RuntimeException e) { throw new MessageHandlingException(message, "Internal server error"); } }
关键说明:
accessor.setUser(authenticatedAuth):把认证成功后的Authentication对象绑定到Header Accessor,这样Spring会自动将其关联到对应的WebSocket会话。accessor.setLeaveMutable(true):默认情况下StompHeaderAccessor是不可变的,必须开启可变才能修改Header信息。- 重新构建Message:原消息的Header是不可变的,所以需要用修改后的Accessor重新创建Message,确保修改的Header能被Spring WebSocket识别。
第二步:在SessionConnectedEvent监听器中获取Principal
完成上面的修改后,你就可以在SessionConnectedEvent的监听器中通过WebSocket会话直接获取Principal了:
@Slf4j @Component @RequiredArgsConstructor public class WebSocketEventListener { @EventListener public void sessionConnectedEvent(SessionConnectedEvent event) { // 获取WebSocket会话对象 WebSocketSession webSocketSession = event.getWebSocketSession(); // 从会话中获取已认证的Principal Principal principal = webSocketSession.getPrincipal(); if (principal != null) { log.info("STOMP会话已建立,当前用户:{}", principal.getName()); // 如果需要获取JWT的详细信息,可以将Principal强转为你的JwtAuthenticationToken if (principal instanceof JwtAuthenticationToken jwtAuthToken) { log.info("当前用户权限:{}", jwtAuthToken.getAuthorities()); // 还可以获取JWT的Claims等信息 // var claims = jwtAuthToken.getToken().getClaims(); } } else { log.warn("SessionConnectedEvent中未获取到已认证用户"); } } }
为什么这样能生效?
SessionConnectedEvent是在STOMP会话完全建立后触发的,此时WebSocket会话已经和之前认证绑定的Authentication对象关联起来了,所以通过webSocketSession.getPrincipal()就能直接拿到已认证的用户信息。
补充说明
你之前尝试的方法没用,核心就是缺失了将认证对象绑定到StompHeaderAccessor并重新构建消息这一步——Spring不会自动把authManager.authenticate()的结果关联到WebSocket会话,必须手动完成绑定。
内容来源于stack exchange
相关产品推荐
相关产品推荐

