通过Spring WebSocket、SockJS和STOMP向已认证用户发送通知
给特定已认证用户推送WebSocket消息的解决方案
看起来你的基础WebSocket配置已经跑通了,现在要实现定向给特定用户推送消息,核心要解决两个问题:一是让后端识别每个WebSocket连接对应的已认证用户,二是通过用户标识精准推送消息。下面是一步步的实现方案:
1. 让WebSocket连接携带JWT并完成认证
你的前端目前连接Stomp时没有传递JWT,导致后端无法把WebSocket会话和具体用户绑定。首先修改前端代码,在Stomp连接时带上JWT:
public connect() : void { let self = this; var token = localStorage.getItem("token");//get jwt from storage self._webSocketUrl = "http://localhost:8080/ws"; let webSocket = new SockJS(this._webSocketUrl); self._stompClient = Stomp.over(webSocket); // 把JWT放到请求头里,和REST接口的认证方式保持一致 const headers = { 'Authorization': `Bearer ${token}` }; self._stompClient.connect(headers, function (frame) { // 新增订阅用户专属通道,用于接收定向通知 self._stompClient.subscribe("/user/notify", function (stompResponse) { console.log("收到专属通知:", stompResponse.body); self._stompSubject.next(stompResponse); }); // 保留原有的通用更新订阅 self._stompClient.subscribe("/realtime", function (stompResponse) { console.log("收到通用更新:", stompResponse.body); self._stompSubject.next(stompResponse); }); }, function(error) { console.error("WebSocket连接失败:", error); }); }
2. 后端配置WebSocket的JWT认证
因为你的应用是用Spring Security+JWT做的REST认证,现在要让WebSocket握手时也能验证JWT。我们可以通过HandshakeInterceptor来提取请求头里的JWT,完成用户认证,并把认证信息绑定到WebSocket会话中:
首先创建JWT握手拦截器:
@Component public class JwtHandshakeInterceptor implements HandshakeInterceptor { @Autowired private JwtTokenProvider jwtTokenProvider; // 替换成你自己的JWT解析工具类 @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { // 从请求头获取Authorization字段 String authorizationHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { String token = authorizationHeader.substring(7); // 验证JWT有效性并提取用户信息 if (jwtTokenProvider.validateToken(token)) { String username = jwtTokenProvider.getUsernameFromToken(token); // 比如你的JWT中存储的邮箱user123@gmail.com // 构建认证对象,存入WebSocket会话属性 Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList()); attributes.put("user", authentication); return true; } } // 允许匿名用户连接(仅能接收通用消息),如果要拒绝未认证连接可以返回false return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { // 无需额外处理 } }
然后修改WebSocket配置,注册拦截器并开启用户专属目的地前缀:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Autowired private JwtHandshakeInterceptor jwtHandshakeInterceptor; @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/realtime", "/user"); // 新增/user前缀,用于用户专属消息路由 config.setApplicationDestinationPrefixes("/app"); config.setUserDestinationPrefix("/user"); // 显式指定用户目的地前缀(默认就是/user,声明后更清晰) } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws") .setAllowedOrigins("*") .addInterceptors(jwtHandshakeInterceptor) // 添加JWT认证拦截器 .withSockJS(); } // 把会话中的认证信息绑定到Stomp消息中,让Spring能识别用户 @Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(new ChannelInterceptor() { @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); if (StompCommand.CONNECT.equals(accessor.getCommand())) { Authentication authentication = (Authentication) accessor.getSessionAttributes().get("user"); if (authentication != null) { accessor.setUser(authentication); } } return message; } }); } }
3. 给特定用户发送消息
现在后端已经能识别每个WebSocket连接对应的用户了,你可以通过SimpMessagingTemplate来给特定用户推送消息。在服务类中注入这个模板即可:
@Service public class NotificationService { @Autowired private SimpMessagingTemplate messagingTemplate; // 给指定用户发送定向通知,参数为用户的用户名/邮箱(比如user123@gmail.com) public void sendNotificationToUser(String username, Object notificationContent) { // convertAndSendToUser会自动把目的地拼接为/user/{username}/notify,精准推送给目标用户 messagingTemplate.convertAndSendToUser(username, "/notify", notificationContent); } }
当某个和user123@gmail.com相关的事件触发时,直接调用这个方法即可:
// 示例:用户订单更新时推送通知 notificationService.sendNotificationToUser("user123@gmail.com", "你的订单已发货,请注意查收!");
关键说明
- 用户目的地前缀:
/user是Spring WebSocket默认的用户专属前缀,调用convertAndSendToUser时框架会自动完成路由,无需手动拼接用户ID。 - 认证绑定逻辑:通过握手拦截器把JWT解析后的用户信息绑定到WebSocket会话,确保Spring能精准识别每个连接所属的用户。
- 兼容性:原有的通用
/realtime订阅不受影响,匿名用户依然可以接收通用更新,仅认证用户能收到专属通知。
内容的提问来源于stack exchange,提问作者Akki
相关产品推荐
相关产品推荐

