⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/weixin_39444768 「zhyoobo」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

前言

在现代 Web 开发中,跨域问题一直是开发者必须面对的挑战。无论是传统的 HTTP 请求还是实时通信的 WebSocket,浏览器的同源策略(Same-Origin Policy)都可能成为功能实现的拦路虎。

许多开发者对 HTTP 的跨域解决方案(如 CORS)已经非常熟悉,但面对 WebSocket 的跨域问题时,常常陷入困惑:“WebSocket 不是基于 TCP 的协议吗?为什么也会有跨域限制?”

本文将深入剖析 WebSocket 的跨域机制,并手把手教你如何通过 Spring Boot 实现安全的 WebSocket 跨域连接。

一、WebSocket 跨域问题的本质

1.1 浏览器安全策略的延伸

WebSocket 协议虽然在传输层基于 TCP,但其握手阶段仍通过 HTTP 完成。浏览器在发起 WebSocket 连接时,会携带 Origin 头字段,标识请求来源。服务器若未明确允许该来源,浏览器会拒绝建立连接。

举个例子:

  • • 前端页面部署在 https://client.com
  • • WebSocket 服务端地址为wss://api.server.com/ws

此时,浏览器会检查 api.server.com 是否允许client.com 的跨域请求。

1.2 与 HTTP CORS 的差异

HTTP 的跨域通过预检请求(Preflight)和响应头(如 Access-Control-Allow-Origin)实现,而 WebSocket 的跨域处理更为简单:

  • 握手阶段验证 Origin 头。
  • 服务器返回 HTTP 101 Switching Protocols表示接受跨域。
  • 没有预检请求,直接通过响应头控制权限。

1.3 典型的跨域错误场景

若未正确配置,浏览器控制台会抛出错误:

WebSocket connection to 'wss://api.server.com/ws' failed: 
Error during WebSocket handshake: Unexpected response code: 403

二、Spring Boot WebSocket 的跨域解决方案

Spring Boot 提供了两种主流的 WebSocket 实现方案: 原生 WebSocket 与 STOMP over WebSocket。

2.1 方案一:原生 WebSocket 跨域配置

2.1.1 实现 HandshakeInterceptor 拦截器

通过拦截握手请求,动态验证 Origin 头:以下分别介绍它们的跨域配置方法。

public classCustomHandshakeInterceptorimplementsHandshakeInterceptor {

@Override
publicbooleanbeforeHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes) {
// 从请求头中获取 Origin
Stringorigin= request.getHeaders().getOrigin();

// 定义允许的域名列表(可改为从配置读取)
List<String> allowedOrigins = Arrays.asList("https://client.com", "http://localhost:8080");

if (allowedOrigins.contains(origin)) {
returntrue; // 允许握手
}
response.setStatusCode(HttpStatus.FORBIDDEN);
returnfalse; // 拒绝连接
}

@Override
publicvoidafterHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Exception exception) {}
}

2.1.2 注册 WebSocket 端点并配置跨域

在配置类中启用 WebSocket 并添加拦截器:

@Configuration
@EnableWebSocket
publicclassWebSocketConfigimplementsWebSocketConfigurer {

@Override
publicvoidregisterWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(newMyWebSocketHandler(), "/ws")
.addInterceptors(newCustomHandshakeInterceptor())
.setAllowedOrigins("*"); // 谨慎使用通配符
}
}

关键注意事项

setAllowedOrigins("*")会允许所有来源,存在安全风险,建议在生产环境中指定具体域名。

若同时使用拦截器和 setAllowedOrigins,拦截器的验证逻辑优先执行。

2.2 方案二:STOMP over WebSocket 跨域配置

对于使用 STOMP 协议的场景(如结合 Spring Messaging),需通过 WebSocketMessageBrokerConfigurer 配置。

2.2.1 基础配置类

@Configuration
@EnableWebSocketMessageBroker
publicclassStompWebSocketConfigimplementsWebSocketMessageBrokerConfigurer {

@Override
publicvoidregisterStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/stomp/ws")
.setAllowedOrigins("https://client.com")
.withSockJS(); // 可选,支持 SockJS 回退
}

@Override
publicvoidconfigureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}

2.2.2 动态 Origin 控制

若需根据请求动态判断,可自定义 DefaultHandshakeHandler:

public classCustomHandshakeHandlerextendsDefaultHandshakeHandler {

@Override
protectedbooleanvalidateOrigin(HttpServletRequest request) {
Stringorigin= request.getHeader("Origin");
// 自定义验证逻辑(例如查询数据库)
return isValidOrigin(origin);
}
}

// 在配置类中替换默认 Handler
registry.addEndpoint("/stomp/ws")
.setHandshakeHandler(newCustomHandshakeHandler());

三、进阶:解决常见疑难问题

3.1 场景:Nginx 反向代理导致跨域失败

若服务端通过 Nginx 转发 WebSocket 请求,需确保代理配置正确:

location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 关键:传递 Origin 头
proxy_set_header Origin $http_origin;
}

3.2 场景:Spring Security 拦截 WebSocket 握手

当项目集成 Spring Security 时,需显式放行 WebSocket 端点:

@Configuration
@EnableWebSecurity
publicclassSecurityConfigextendsWebSecurityConfigurerAdapter {

@Override
protectedvoidconfigure(HttpSecurity http)throws Exception {
http.authorizeRequests()
.antMatchers("/ws/**").permitAll() // 放行 WebSocket 端点
.anyRequest().authenticated();
}
}

3.3 场景:SockJS 的跨域兼容性

使用 SockJS 时,浏览器可能发起 OPTIONS 预检请求,需确保 CORS 配置覆盖:

// 针对 /info 端点的预检请求
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("https://client.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS");
}

四、安全实践:避免跨域配置中的陷阱

4.1 风险:滥用通配符 * 的后果

  • CSRF 攻击:恶意网站可通过 WebSocket 窃取会话信息。
  • 数据泄露:未授权域名获取实时推送数据。

解决方案:

  • 通过环境变量动态加载允许的域名列表。
  • 结合 OAuth 2.0 在握手阶段验证 Token。

4.2 防御:WebSocket 层的身份验证

在握手拦截器中实现权限校验:

public boolean beforeHandshake(...) {
String token = request.getHeaders().getFirst("Authorization");
if (!jwtService.validateToken(token)) {
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return false;
}
return true;
}

五、测试与验证

5.1 使用 Postman 测试跨域

Postman 支持手动设置 Origin 头:

  • 新建 WebSocket 请求,URL 输入 wss://api.server.com/ws
  • 在 Headers 中添加 Origin: https://unauthorized-site.com
  • 观察连接是否被拒绝

5.2 浏览器端验证

在前端代码中捕获连接错误:

const socket = new WebSocket('wss://api.server.com/ws');
socket.onerror = (error) => {
console.log('Connection failed:', error);
};

六、总结

WebSocket 的跨域问题本质上是浏览器安全策略对握手阶段的约束。在 Spring Boot 中,通过合理配置 HandshakeInterceptor、WebSocketConfigurer 或 WebSocketMessageBrokerConfigurer,开发者可以灵活控制跨域行为。关键要点包括:

  • 避免使用通配符,尽量动态验证 Origin。
  • 区分 HTTP CORS 与 WebSocket 跨域,二者需独立配置。
  • 结合安全框架,在握手阶段实现身份认证。

通过本文的实践方案,开发者不仅能解决跨域问题,还能构建更健壮的实时通信系统。

附录:常见问题速查表

问题现象 可能原因 解决方案
连接返回 403 Forbidden Origin 未在允许列表中 检查 setAllowedOrigins 配置
首次连接成功,重连失败 反向代理未保持 WebSocket 头 配置 Nginx 的 proxy_set_header
前端控制台报 “Invalid UTF-8” 未正确处理二进制消息 配置 BinaryType 或使用 STOMP

掌握这些技巧后,WebSocket 跨域将不再是阻碍实时通信的绊脚石。

文章目录
  1. 1. 前言
  2. 2. 一、WebSocket 跨域问题的本质
    1. 2.0.1. 1.1 浏览器安全策略的延伸
      1. 2.0.1.1. 举个例子:
    2. 2.0.2. 1.2 与 HTTP CORS 的差异
    3. 2.0.3. 1.3 典型的跨域错误场景
  • 3. 二、Spring Boot WebSocket 的跨域解决方案
    1. 3.0.1. 2.1 方案一:原生 WebSocket 跨域配置
      1. 3.0.1.1. 2.1.1 实现 HandshakeInterceptor 拦截器
      2. 3.0.1.2. 2.1.2 注册 WebSocket 端点并配置跨域
    2. 3.0.2. 2.2 方案二:STOMP over WebSocket 跨域配置
      1. 3.0.2.1. 2.2.1 基础配置类
      2. 3.0.2.2. 2.2.2 动态 Origin 控制
  • 4. 三、进阶:解决常见疑难问题
    1. 4.0.1. 3.1 场景:Nginx 反向代理导致跨域失败
    2. 4.0.2. 3.2 场景:Spring Security 拦截 WebSocket 握手
    3. 4.0.3. 3.3 场景:SockJS 的跨域兼容性
  • 5. 四、安全实践:避免跨域配置中的陷阱
    1. 5.0.1. 4.1 风险:滥用通配符 * 的后果
      1. 5.0.1.1. 解决方案:
    2. 5.0.2. 4.2 防御:WebSocket 层的身份验证
  • 6. 五、测试与验证
    1. 6.0.1. 5.1 使用 Postman 测试跨域
    2. 6.0.2. 5.2 浏览器端验证
  • 7. 六、总结
    1. 7.0.0.1. 附录:常见问题速查表