Spring WebSockets如何验证消息并筛选接收客户端?
当然支持啦!Spring WebSockets给你提供了好几种灵活的方式来实现这种「仅指定客户端接收敏感消息」的需求,我给你整理了几个常用的实现方案:
这是最通用的方式——通过拦截入站/出站消息,在消息发送前校验客户端权限,决定是否放行。你可以实现ChannelInterceptor接口,在消息到达Broker之前做过滤:
@Component public class SensitiveMessageFilter implements ChannelInterceptor { @Override public Message<?> preSend(Message<?> message, MessageChannel channel) { StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); // 只处理发送到主题的消息(根据你的场景调整) if (StompCommand.SEND.equals(accessor.getCommand()) || StompCommand.MESSAGE.equals(accessor.getCommand())) { // 解析消息内容,提取敏感标识和允许接收的客户端信息 String payload = new String((byte[]) message.getPayload()); // 获取当前客户端的会话ID或用户信息(如果已经和用户绑定) String clientSessionId = accessor.getSessionId(); String currentUser = accessor.getUser() != null ? accessor.getUser().getName() : null; // 替换成你的权限校验逻辑:比如判断当前客户端是否在允许接收的列表里 if (!isAuthorized(currentUser, clientSessionId, payload)) { // 无权限则拦截消息,返回null即可 return null; } } return message; } private boolean isAuthorized(String user, String sessionId, String payload) { // 这里写你的具体校验逻辑:比如从payload里提取允许的用户ID/会话ID,和当前客户端匹配 // 示例:假设payload里包含"allowedClients"字段,解析后判断是否包含当前sessionId return true; } }
然后在WebSocket配置类里注册这个拦截器:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Autowired private SensitiveMessageFilter sensitiveMessageFilter; @Override public void configureClientInboundChannel(ChannelRegistration registration) { // 注册拦截器处理入站消息 registration.interceptors(sensitiveMessageFilter); } // 其他WebSocket配置,比如设置代理、允许的起源等... @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic", "/queue"); config.setApplicationDestinationPrefixes("/app"); } }
如果你的敏感消息本来就是要发给特定用户,而不是广播到主题,那直接用@SendToUser会更简单——它会把消息发送到该用户专属的目的地(格式为/user/{username}/queue/xxx),只有该用户的客户端能收到。同时你可以在发送前做权限校验:
@Controller public class ContractMessageController { @MessageMapping("/app/send-sensitive-contract") @SendToUser("/queue/sensitive-contracts") public String sendSensitiveContract(@Payload String contractMessage, Principal principal) { // 校验当前用户是否有权限接收这条消息 if (!hasPermissionToView(principal.getName(), contractMessage)) { throw new AccessDeniedException("你无权查看该敏感合约消息"); } return contractMessage; } private boolean hasPermissionToView(String username, String contractMessage) { // 你的权限逻辑:比如解析合约ID,判断用户是否属于该合约的授权查看者 return true; } }
客户端订阅的时候,只需要订阅/user/queue/sensitive-contracts就能收到自己专属的敏感消息,不用再担心其他客户端获取到。
如果你的需求是广播到/topic/contract/{contractId}这类主题,但需要给每个订阅该主题的客户端做权限校验,那可以自定义Broker的消息处理逻辑。比如扩展SimpleBrokerMessageHandler,在广播消息前逐个判断订阅者是否有权限:
public class SecuredSimpleBrokerMessageHandler extends SimpleBrokerMessageHandler { public SecuredSimpleBrokerMessageHandler(MessageBrokerRegistry registry, SubscribableChannel clientOutboundChannel, TaskScheduler scheduler) { super(registry, clientOutboundChannel, scheduler); } @Override protected void sendMessageToSubscribers(String destination, Message<?> message, Set<String> sessionIds) { // 过滤出有权限接收该消息的会话ID Set<String> authorizedSessionIds = new HashSet<>(); for (String sessionId : sessionIds) { // 获取会话对应的用户信息,判断是否有权限接收当前消息 if (isSessionAuthorized(sessionId, destination, message)) { authorizedSessionIds.add(sessionId); } } // 只给有权限的会话发送消息 super.sendMessageToSubscribers(destination, message, authorizedSessionIds); } private boolean isSessionAuthorized(String sessionId, String destination, Message<?> message) { // 这里写你的校验逻辑:比如根据destination里的contractId,判断该会话用户是否有权限查看 return true; } }
然后在配置类里替换默认的Broker处理器:
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic", "/queue"); config.setApplicationDestinationPrefixes("/app"); } @Bean public SimpleBrokerMessageHandler simpleBrokerMessageHandler(MessageBrokerRegistry registry, @Qualifier("clientOutboundChannel") SubscribableChannel clientOutboundChannel, TaskScheduler messageBrokerTaskScheduler) { return new SecuredSimpleBrokerMessageHandler(registry, clientOutboundChannel, messageBrokerTaskScheduler); } }
这个方案适合需要对广播主题做细粒度权限控制的场景,但实现起来相对复杂一些。
总结一下:这三个方案覆盖了大部分场景——通用消息拦截用方案一,特定用户发送用方案二,广播主题细粒度过滤用方案三。你可以根据自己的业务需求选择最合适的方式。
内容的提问来源于stack exchange,提问作者Val Schuman

