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

通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:50:19