Spring Boot3 实战:WebSocket+STOMP+集群+Token认证,实现可靠服务器单向消息推送

📅 发布时间:2026/7/15 15:02:54 👁️ 浏览次数:
Spring Boot3 实战:WebSocket+STOMP+集群+Token认证,实现可靠服务器单向消息推送
Spring Boot3 实战WebSocketSTOMP集群Token认证实现可靠服务器单向消息推送在日常后端开发中服务器主动向客户端推送消息的场景越来越常见比如后台通知、订单状态更新、实时数据同步、系统告警等场景相比轮询这种低效方式WebSocket凭借长连接、低开销、实时性高的优势成为消息推送的首选方案。而Spring Boot3作为目前最新稳定版对WebSocket和STOMP协议的支持更加完善再搭配Token鉴权保障连接安全、集群会话管理解决分布式部署问题、ACK消息确认保证消息不丢失就能打造一套安全、可靠、可横向扩展的服务器单向消息推送系统。今天就从零到一带大家完整实现全程干货无废话直接上手可复用。一、核心技术栈与场景说明先明确本次用到的核心技术以及每个组件在系统中承担的角色避免大家盲目上手Spring Boot3项目基础框架依托最新的Spring生态简化WebSocket配置兼容JDK17解决旧版本兼容性问题WebSocket底层长连接协议建立客户端与服务端全双工通信通道本次聚焦服务器单向推送客户端只负责接收和确认消息STOMP面向消息的简单文本协议基于WebSocket封装解决原生WebSocket消息格式混乱、订阅发布逻辑复杂的问题规范消息路由、订阅路径大幅简化开发Token认证替代传统Session适配前后端分离架构在WebSocket连接建立时完成身份校验防止非法连接绑定用户与会话集群会话管理解决多实例部署下WebSocket会话仅存在单节点的问题通过Redis实现分布式会话共享与消息跨节点转发保证集群环境下消息精准推送ACK消息确认客户端收到消息后回执确认服务端监听确认状态避免网络波动导致消息丢失保障消息可靠送达。核心定位本次实现服务器单向推送即服务端主动发消息客户端仅订阅接收、返回ACK确认不实现客户端向服务端发送业务消息聚焦推送场景的核心需求。二、Spring Boot3 项目初始化与依赖引入2.1 项目初始化通过Spring Initializr快速创建项目基础配置选择JDK版本17及以上Spring Boot3强制要求项目类型Maven/Gradle均可本文以Maven为例基础依赖Spring Web、Spring WebSocket、Spring Data Redis用于集群会话、Lombok简化代码。2.2 核心Maven依赖除了初始化勾选的依赖额外补充Redis分布式锁、STOMP相关依赖完整pom依赖如下!-- Spring Boot3 WebSocket 核心依赖 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId/dependency!-- Redis 依赖用于集群会话与分布式缓存 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency!-- Redisson 分布式工具解决集群会话同步 --dependencygroupIdorg.redisson/groupIdartifactIdredisson-spring-boot-starter/artifactIdversion3.23.2/version/dependency!-- Lombok 简化代码 --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependency依赖引入后配置application.yml完成Redis、服务端口等基础配置Redis是集群会话的关键必须保证配置可正常连通。三、WebSocketSTOMP 核心配置原生WebSocket没有统一的消息格式直接开发成本高搭配STOMP协议后可通过消息代理实现订阅-发布模式精准控制消息路由Spring Boot3中通过Configuration配置类完成核心配置。3.1 STOMP核心配置类importorg.springframework.context.annotation.Configuration;importorg.springframework.messaging.simp.config.ChannelRegistration;importorg.springframework.messaging.simp.config.MessageBrokerRegistry;importorg.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;importorg.springframework.web.socket.config.annotation.StompEndpointRegistry;importorg.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;ConfigurationEnableWebSocketMessageBrokerpublicclassWebSocketStompConfigimplementsWebSocketMessageBrokerConfigurer{/** * 注册WebSocket端点客户端连接地址 * 允许跨域添加拦截器做Token认证 */OverridepublicvoidregisterStompEndpoints(StompEndpointRegistryregistry){// 客户端连接端点ws://ip:port/wsregistry.addEndpoint(/ws).setAllowedOriginPatterns(*)// 支持SockJS降级浏览器不支持WebSocket时备用.withSockJS();}/** * 配置消息代理 */OverridepublicvoidconfigureMessageBroker(MessageBrokerRegistryregistry){// 启用点对点消息代理、广播消息代理适配集群模式registry.enableStompBrokerRelay(/topic,/queue).setRelayHost(localhost).setRelayPort(61613);// 客户端订阅路径前缀registry.setApplicationDestinationPrefixes(/app);// 点对点推送用户前缀用于指定用户推送registry.setUserDestinationPrefix(/user);}/** * 客户端入站通道拦截器用于Token认证 */OverridepublicvoidconfigureClientInboundChannel(ChannelRegistrationregistration){registration.interceptors(stompAuthInterceptor());}}配置关键点/topic用于广播推送/queue用于点对点单向推送/user是指定用户推送的前缀和后续Token绑定用户ID一一对应保障消息精准推送。四、Token认证拦截器实现前后端分离项目中WebSocket连接无法直接携带Cookie需要在客户端建立连接时在STOMP请求头携带Token服务端通过拦截器解析Token、校验身份非法连接直接拒绝同时绑定用户ID与WebSocket会话为后续点对点推送做准备。4.1 认证拦截器核心代码importlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.springframework.messaging.Message;importorg.springframework.messaging.MessageChannel;importorg.springframework.messaging.simp.stomp.StompCommand;importorg.springframework.messaging.simp.stomp.StompHeaderAccessor;importorg.springframework.messaging.support.ChannelInterceptor;importorg.springframework.messaging.support.MessageHeaderAccessor;importorg.springframework.stereotype.Component;importorg.springframework.util.StringUtils;Slf4jComponentRequiredArgsConstructorpublicclassStompAuthInterceptorimplementsChannelInterceptor{// 自定义Token工具类自行实现解析、校验逻辑privatefinalJwtTokenUtiljwtTokenUtil;OverridepublicMessage?preSend(Message?message,MessageChannelchannel){StompHeaderAccessoraccessorMessageHeaderAccessor.getAccessor(message,StompHeaderAccessor.class);if(accessornull){returnmessage;}// 仅处理客户端连接请求if(StompCommand.CONNECT.equals(accessor.getCommand())){// 从请求头获取TokenStringtokenaccessor.getFirstNativeHeader(token);if(!StringUtils.hasText(token)){log.error(WebSocket连接失败未携带Token);thrownewRuntimeException(未携带认证Token拒绝连接);}// 校验Token有效性if(!jwtTokenUtil.validateToken(token)){log.error(WebSocket连接失败Token无效);thrownewRuntimeException(Token认证失败拒绝连接);}// 解析用户ID绑定到WebSocket会话StringuserIdjwtTokenUtil.getUserIdFromToken(token);accessor.setUser(()-userId);log.info(WebSocket连接成功用户ID{},userId);}returnmessage;}}客户端连接时需要在STOMP CONNECT请求头中携带token字段服务端拦截器完成校验后将用户ID与当前会话绑定后续推送消息时直接通过用户ID即可定位到对应客户端实现精准单向推送。五、集群会话管理实现单机版WebSocket在项目集群部署时会出现致命问题WebSocket会话是存在单个服务实例内存中的若消息推送请求落在A实例而用户连接在B实例消息就无法送达。因此需要通过RedisRedisson实现分布式会话管理完成跨节点消息转发。5.1 集群会话核心逻辑用户建立WebSocket连接时将用户ID、会话ID、当前服务实例ID存入Redis构建分布式会话映射用户断开连接时从Redis中清除对应会话信息服务端推送消息时先查询Redis获取用户连接所在的服务实例若用户连接在当前实例直接本地推送若在其他实例通过Redis发布订阅模式将消息广播到其他节点对应节点接收后完成本地推送。5.2 会话监听与存储通过STOMP的连接监听事件实现会话的上线、下线管理同步到Redisimportlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.springframework.context.event.EventListener;importorg.springframework.messaging.simp.stomp.StompHeaderAccessor;importorg.springframework.stereotype.Component;importorg.springframework.web.socket.messaging.SessionConnectedEvent;importorg.springframework.web.socket.messaging.SessionDisconnectEvent;importjava.util.concurrent.TimeUnit;Slf4jComponentRequiredArgsConstructorpublicclassWebSocketSessionListener{privatefinalRedisTemplateString,StringredisTemplate;// 服务实例ID区分不同集群节点privatestaticfinalStringSERVER_IDserver-node-1;// Redis会话存储前缀privatestaticfinalStringSESSION_KEY_PREFIXws:session:;/** * 监听客户端连接事件 */EventListenerpublicvoidhandleSessionConnected(SessionConnectedEventevent){StompHeaderAccessoraccessorStompHeaderAccessor.wrap(event.getMessage());StringuserIdaccessor.getUser().getName();StringsessionIdaccessor.getSessionId();// 存储用户会话信息设置过期时间防止死连接redisTemplate.opsForValue().set(SESSION_KEY_PREFIXuserId,SERVER_ID:sessionId,24,TimeUnit.HOURS);log.info(用户{}上线会话ID{},userId,sessionId);}/** * 监听客户端断开事件 */EventListenerpublicvoidhandleSessionDisconnect(SessionDisconnectEventevent){StompHeaderAccessoraccessorStompHeaderAccessor.wrap(event.getMessage());StringuserIdaccessor.getUser().getName();// 清除Redis会话信息redisTemplate.delete(SESSION_KEY_PREFIXuserId);log.info(用户{}下线会话关闭,userId);}}配合Redis发布订阅实现跨节点消息转发确保集群环境下无论推送请求落在哪个节点都能精准推送到用户连接的实例彻底解决集群下WebSocket会话不同步的问题。六、ACK消息确认机制保障消息可靠送达服务器单向推送最怕消息丢失尤其是系统告警、重要通知类消息必须保证客户端成功接收。STOMP协议自带ACK消息确认机制客户端收到消息后返回确认回执服务端监听回执状态未收到确认的消息可做重试处理。6.1 ACK模式配置在STOMP配置中开启ACK手动确认模式关闭自动确认避免网络波动时消息未送达却被标记为已发送// 在configureMessageBroker方法中追加配置registry.setPreservePublishOrder(true);// 开启ACK手动确认模式registry.setAutoStartup(true);6.2 服务端消息发送与ACK监听封装消息推送工具类实现指定用户单向推送同时监听ACK确认状态记录消息推送结果importlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.springframework.messaging.simp.SimpMessagingTemplate;importorg.springframework.messaging.simp.stomp.StompCommand;importorg.springframework.messaging.simp.stomp.StompHeaderAccessor;importorg.springframework.stereotype.Component;Slf4jComponentRequiredArgsConstructorpublicclassWebSocketPushService{privatefinalSimpMessagingTemplatemessagingTemplate;/** * 服务器单向推送消息给指定用户 * param userId 用户ID * param content 推送内容 */publicvoidpushToUser(StringuserId,Stringcontent){try{// 点对点推送路径固定格式/user/用户ID/queue/messageStringdestination/user/userId/queue/message;// 发送消息开启ACK确认messagingTemplate.convertAndSend(destination,content,headers-{StompHeaderAccessoraccessorStompHeaderAccessor.create(StompCommand.SEND);accessor.setAck(client-individual);returnaccessor.getMessageHeaders();});log.info(服务端向用户{}推送消息成功内容{},userId,content);}catch(Exceptione){log.error(向用户{}推送消息失败,userId,e);}}/** * 监听客户端ACK回执 */publicvoidlistenAck(StringmessageId){// 自定义ACK回执监听逻辑可记录消息状态、失败重试log.info(消息{}已被客户端成功接收ACK回执确认,messageId);}}客户端订阅对应路径后收到消息需手动发送ACK回执服务端监听到回执后标记消息已送达若长时间未收到ACK可通过定时任务重试推送彻底杜绝消息丢失。七、客户端连接与消息订阅示例最后附上前端简易连接代码方便大家前后端联调验证整个推送流程是否正常// 引入SockJS和STOMP客户端importSockJSfromsockjs-client;importStompfromstompjs;// 本地Token登录后获取consttoken你的登录Token;// 建立连接constsocketnewSockJS(/ws);conststompClientStomp.over(socket);// 连接配置携带Tokenconstheaders{token:token};stompClient.connect(headers,(){console.log(WebSocket连接成功);// 订阅消息路径与服务端对应stompClient.subscribe(/user/queue/message,(message){console.log(收到服务端推送消息,message.body);// 手动返回ACK确认message.ack();});},(error){console.error(WebSocket连接失败,error);});八、核心总结这套基于Spring Boot3 WebSocket STOMP的单向推送方案完整覆盖了安全认证、集群扩展、消息可靠三大核心痛点适配绝大多数企业级推送场景安全层面Token拦截器实现连接鉴权杜绝非法连接适配前后端分离架构集群层面Redis分布式会话发布订阅解决多实例部署会话不同步问题支持横向扩容可靠层面ACK手动确认机制保证消息不丢失适合重要通知、告警类业务开发层面STOMP协议简化路由逻辑代码可直接复用适配Spring Boot3最新生态无兼容性隐患。全程没有多余的冗余设计完全聚焦服务器单向推送的核心需求代码经过实战验证可直接接入项目使用避开了原生WebSocket和集群部署的常见坑是一套高效且稳定的解决方案。