Prompts-MCP 686 skills · ai / design / design-pattern / framework / fundamentals / habit / lang / tech-selection /mcp/sse
framework framework/websocket/spring-websocket.md medium

websocket-spring-websocket

Spring WebSocket 接入选型 — JSR-356 '@ServerEndpoint' vs Spring 原生 WebSocketHandler/握手拦截器/WebSocketSession 会话操作。Use when 接入 WebSocket / 选端点写法 / 写握手拦截器时。

握手拦截器websocket 端点@serverendpointwebsockethandlerhandshakeinterceptor
paths
  • *.java

Spring WebSocket · 端点选型与握手

本条只管「裸 WebSocket 怎么接入、握手怎么拦」。子协议消息路由见 stomp-messaging.md;session 与用户映射见 session-and-heartbeat.md

规则

约定
端点选型 Spring 项目优先 WebSocketHandler(接入 Spring 生命周期/拦截器/容器);@ServerEndpoint 仅在脱离 Spring 的纯 JSR-356 容器下用
注册方式 实现 WebSocketConfigurer,在 registerWebSocketHandlers 注册 handler + 路径 + 拦截器
处理器基类 文本消息继承 TextWebSocketHandler,二进制继承 BinaryWebSocketHandler,别直接裸实现接口
握手鉴权 HandshakeInterceptor.beforeHandshake 校验 token,把 userId 放进 attributes(后续 session 可取)
跨域 setAllowedOrigins 显式列白名单,禁止 "*" 上生产
@ServerEndpoint 注入 @ServerEndpoint 默认非 Spring Bean,注入 Service 需 SpringConfigurator 或静态持有

正例

@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WsConfig implements WebSocketConfigurer {
    private final ChatHandler chatHandler;
    private final AuthHandshakeInterceptor authInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatHandler, "/ws/chat")
                .addInterceptors(authInterceptor)
                .setAllowedOrigins("https://app.example.com");  // 不写 "*"
    }
}

@Component
public class ChatHandler extends TextWebSocketHandler {
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        Object uid = session.getAttributes().get("userId");   // 握手时塞入
        // 注册到用户↔session 映射,见 session-and-heartbeat
    }
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage msg) throws Exception {
        session.sendMessage(new TextMessage("echo:" + msg.getPayload()));
    }
}
// 握手拦截器:鉴权 + 把用户身份带进 session.attributes
@Component
public class AuthHandshakeInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest req, ServerHttpResponse resp,
                                   WebSocketHandler h, Map<String, Object> attributes) {
        String token = UriComponentsBuilder.fromUri(req.getURI()).build()
                .getQueryParams().getFirst("token");
        Long uid = tokenService.parse(token);  // 失败返回 false → 拒绝握手
        if (uid == null) { resp.setStatusCode(HttpStatus.UNAUTHORIZED); return false; }
        attributes.put("userId", uid);
        return true;
    }
    @Override public void afterHandshake(ServerHttpRequest r, ServerHttpResponse s,
                                         WebSocketHandler h, Exception e) {}
}

反例

// ❌ setAllowedOrigins("*"):任意站点可发起跨域 WS 连接,CSRF 风险
registry.addHandler(handler, "/ws").setAllowedOrigins("*");

// ❌ 把鉴权放进 handleTextMessage:连接已建立才验,资源已占且可绕过
@Override protected void handleTextMessage(WebSocketSession s, TextMessage m) {
    if (!auth(m.getPayload())) s.close();   // 鉴权应在握手期做
}

自检

  • [ ] Spring 项目用 WebSocketHandler 体系而非 @ServerEndpoint
  • [ ] 鉴权在 HandshakeInterceptor.beforeHandshake 完成、失败即拒握手?
  • [ ] 用户身份握手时放进 attributes,而非连接后再传?
  • [ ] setAllowedOrigins 列了白名单、没用 "*"

相关