第6篇:websocket 频道消息拦截器ChannelInterceptor 介绍和使用
ChannelInterceptor是 Spring WebSocket 中用于拦截消息通道流转的核心组件主要作用是在消息发送 或 接收的关键节点插入自定义逻辑。核心作用拦截客户端与服务端之间的 STOMP 消息流支持在消息处理前、后或完成时执行逻辑典型场景入站拦截preSend权限校验、敏感词过滤、内容审计、防刷限流出站拦截preSend消息脱敏、统一字段增强、推送日志记录资源清理afterSendCompletion释放上下文、监控埋点关键方法preSend()最常用返回null可拦截消息仅入站通道需处理用户会话和命令出站通道无会话属性用途不同最佳实践入站与出站逻辑分离使用不同拦截器公共逻辑如敏感词检测抽成工具类避免重复出站一般不用于拦截而用于增强或告警示例场景对客户端发的非法消息进行拦截1 写消息拦截器import org.springframework.lang.Nullable; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.simp.stomp.StompCommand; import org.springframework.messaging.simp.stomp.StompHeaderAccessor; import org.springframework.messaging.support.ChannelInterceptor; import org.springframework.stereotype.Component; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Set; Component public class SocketChannelInterceptor implements ChannelInterceptor { // 敏感词集合使用 Set 提升查找效率O(1) private static final SetString SENSITIVE_WORDS_SET Set.of(赌博, 诈骗, 色情, fuck, 违禁, 黑产); /** * 消息即将被发送到通道前 * param message * param channel * return */ Nullable Override public Message? preSend(Message? message, MessageChannel channel) { System.out.println(频道拦截器-preSend); // 1. 快速判断如果不是 STOMP 相关的消息例如底层心跳包直接放行 if (message null) { return message; } // 包装为 STOMP 消息头访问器 StompHeaderAccessor accessor StompHeaderAccessor.wrap(message); // 2. 如果命令为空说明不是标准的 STOMP 帧比如 CONNECT 之后的心跳直接放行 if (accessor.getCommand() null) { return message; } // 3. 只处理 SEND 类型的消息即客户端主动发送的消息 if (!StompCommand.SEND.equals(accessor.getCommand())) { return message; // 其他类型如 SUBSCRIBE、DISCONNECT 等不处理直接放行 } // 4. 安全地从会话属性中获取用户名username 是在 HandshakeInterceptor 中存入的 String username anonymous; MapString, Object sessionAttrs accessor.getSessionAttributes(); if (sessionAttrs ! null sessionAttrs.containsKey(username)) { Object userObj sessionAttrs.get(username); if (userObj ! null) { username userObj.toString(); } } // 获取消息目的地和原始负载payload String destination accessor.getDestination(); Object payload message.getPayload(); // 5. 安全提取消息内容支持 String、byte[] 等常见类型 String content extractContent(payload); // 6. 打印日志生产环境建议使用 SLF4J 并控制日志级别 System.out.printf([消息拦截] 用户: %s | 发送到: %s | 内容: %s%n, username, destination, content); // 7. 检查是否包含敏感词若包含则拦截 if (containsSensitiveWord(content)) { System.out.printf( 拦截含敏感词的消息 - 用户: %s, 目标: %s, 内容: %s%n, username, destination, content); return null; // 返回 null 表示丢弃该消息客户端不会收到响应 } // 8. 无敏感词放行消息 return message; } // 辅助方法 /** * 安全地从消息负载payload中提取可读的字符串内容 * * param payload 消息负载可能是 String、byte[] 或其他对象 * return 提取后的字符串内容若无法提取则返回空字符串或错误提示 */ private String extractContent(Object payload) { if (payload null) { return ; } if (payload instanceof String str) { return str; } if (payload instanceof byte[] bytes) { // 明确使用 UTF-8 编码解码防止中文乱码 return new String(bytes, StandardCharsets.UTF_8); } // 兜底方案尝试调用 toString()适用于自定义 POJO但需注意安全性 try { return payload.toString(); } catch (Exception e) { return [内容提取失败]; } } /** * 判断消息内容是否包含敏感词 * * param content 消息文本内容 * return 如果包含任意敏感词返回 true否则返回 false */ private boolean containsSensitiveWord(String content) { if (content null || content.isEmpty()) return false; String cleanContent content.replaceAll(\\s, ) // 去掉空格 .toLowerCase(); // 转小写针对英文 // 遍历敏感词集合只要命中一个就返回 true for (String word : SENSITIVE_WORDS_SET) { String normalizedWord word.toLowerCase(); if (cleanContent.contains(normalizedWord)) { return true; } } return false; } /** * 发送消息调用后立即调用 * ---- 做测试用的可以去掉 ---- */ Override public void postSend(Message? message, MessageChannel channel, boolean sent) { System.out.println(频道拦截器-postSend); ChannelInterceptor.super.postSend(message,channel,sent); } /** * 在完成发送之后进行调用不管是否有异常发生一般用于资源清理 * ---- 做测试用的可以去掉 ---- */ Override public void afterSendCompletion(Message? message, MessageChannel channel, boolean sent, Nullable Exception ex) { System.out.println(频道拦截器-afterSendCompletion); ChannelInterceptor.super.afterSendCompletion(message, channel, sent, ex); } }2 配置消息拦截器Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry registry) { } Override public void registerStompEndpoints(StompEndpointRegistry registry) { } Autowired private SocketChannelInterceptor socketChannelInterceptor; /** * 入站通道Client Inbound Channel客户端 → 服务端 * 配置 自己写的频道拦截器 */ Override public void configureClientInboundChannel(ChannelRegistration registration) { registration.interceptors(socketChannelInterceptor); } }