WebSocket出现403 Forbidden,90%非权限问题,而是握手阶段被拦截:Spring未放行Origin、Nginx丢弃Upgrade/Connection头,或Spring Security未豁免WebSocket路径;需分别配置allowedOrigins(禁用*)、Nginx三要素代理头及SecurityFilterChain显式permitAll对应路径。

WebSocket 出现 403 Forbidden,90% 不是权限问题,而是握手阶段被拦截——要么 Spring 拒绝了 Origin,要么 Nginx 丢掉了 Upgrade 和 Connection 头,要么 Spring Security 没放行 WebSocket 路径。直接看怎么修。
Spring Boot 原生 WebSocket 的 setAllowedOrigins 配置失效?
很多开发者调用 registry.addHandler(...).setAllowedOrigins("*") 后仍报 403,原因很实际:
-
setAllowedOrigins("*")在 Spring Boot 2.5+ 中已被标记为不推荐,且对带凭据(withCredentials: true)的请求会直接拒绝(浏览器强制要求不能用*) - 若前端用
ws://127.0.0.1:8080/ws,但后端收到的Origin是http://localhost:3000,"*"不匹配这个具体值 - 没启用
@EnableWebSocket注解,配置类压根没被扫描到
正确做法是显式列出可信源,或用拦截器动态校验:
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
WebSocketHandler wsHandler, Map<string object> attributes) {
String origin = request.getHeaders().getOrigin();
if (origin != null && (origin.contains("localhost") || origin.contains("yourdomain.com"))) {
response.getHeaders().set("Access-Control-Allow-Origin", origin);
response.getHeaders().set("Access-Control-Allow-Credentials", "true");
return true;
}
return false;
}</string>
Nginx 反向代理下 WebSocket 返回 403?检查 proxy_set_header
本地直连 WebSocket 正常,Nginx 后就 Expected HTTP 101 response but was '403 Forbidden' ——这是典型代理头丢失。Nginx 默认不转发 Upgrade 和 Connection,导致后端根本收不到升级请求,直接按普通 HTTP 处理并拒掉。
必须在 location 块中补全三要素:
-
proxy_http_version 1.1:WebSocket 要求 HTTP/1.1 -
proxy_set_header Upgrade $http_upgrade:透传客户端的Upgrade: websocket -
proxy_set_header Connection "$connection_upgrade":注意这里不是固定字符串,要配合map指令处理空值
完整片段示例(放在 http 块和 server 块中):
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "$connection_upgrade";
proxy_set_header Host $host;
}
}
Spring Security 拦截 WebSocket 路径导致 403?
即使 WebSocket 握手配置全对,只要路径被 Spring Security 拦住,照样返回 403。常见于用了 WebSecurityConfigurerAdapter(旧版)或 SecurityFilterChain(新版)但没放行 WebSocket 端点。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
关键点:
- Spring Security 默认拦截所有路径,包括
/ws、/websocket这类非 REST 接口 -
CorsConfiguration的跨域配置对 WebSocket 无效,它只管 HTTP 请求;WebSocket 跨域靠的是握手阶段的Origin校验 - 必须显式放行 WebSocket 相关路径,且建议排除掉 SockJS 的 info 请求(如
/ws/info)
新版配置(SecurityFilterChain)示例:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authz -> authz
.requestMatchers("/ws/**", "/ws/info").permitAll() // 放行 WebSocket 路径
.requestMatchers("/api/**").authenticated()
);
return http.build();
}
微信小程序或网关环境下 Origin 动态变化?
Postman 测试成功,但小程序连接报 403,是因为小程序发起 WebSocket 请求时,Origin 头不是固定值——它随当前页面来源变化(如开发版、体验版、正式版域名不同),甚至某些版本会设为 null。
此时硬编码 allowedOrigins 或静态白名单必然失败。可行方案只有两个:
- 前端在
wx.connectSocket中手动指定header: { Origin: "https://yourdomain.com" },让后端能稳定识别 - 后端拦截器完全忽略
Origin校验(仅限内网或可信环境),直接返回Access-Control-Allow-Origin: *并设Access-Control-Allow-Credentials: false
特别注意:Spring Cloud Gateway 的全局 CORS 配置(allowedOriginPatterns: "*")对 WebSocket 流量基本无效——网关不解析 WebSocket 握手帧,它只做 TCP 层转发,跨域控制必须落在业务服务层。










