当前位置: 首页 > news >正文

Spring Security 实战:彻底解决 CORS 跨域凭据问题与 WebSocket 连接失败

在前后端分离架构中,跨域(CORS)问题是开发者绕不开的“坑”,尤其是当请求需要携带凭据(如 Cookie、Token)时,稍有配置不当就会触发浏览器拦截。本文结合实际项目中遇到的“CORS 凭据不匹配”“WebSocket 403 拒绝访问”等问题,详细讲解如何在 Spring Security 中正确配置跨域,以及避坑要点。

一、问题背景:从报错日志看核心矛盾

项目中先后出现两类跨域相关错误,本质都是配置不兼容导致:

1. CORS 凭据不匹配错误

前端请求携带 withCredentials: true(需传递 Cookie/Token)时,浏览器报错:

Access to XMLHttpRequest at 'http://127.0.0.1:8080/api/ws/info' from origin 'null' has been blocked by CORS policy: 
The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'.

核心原因:后端未返回 Access-Control-Allow-Credentials: true 响应头,与前端凭据模式不匹配。

2. WebSocket 连接失败与 403 错误

WebSocket 请求(如 ws://127.0.0.1:8080/api/ws/123/abc/websocket)被拦截,同时出现:

  • WebSocket connection to 'ws://...' failed
  • GET http://.../eventsource 403 (Forbidden)
    核心原因:Spring Security 未放行 WebSocket 相关路径,且 WebSocket 未单独配置跨域。

二、解决方案:Spring Security 跨域配置实战

针对上述问题,需从“HTTP 跨域凭据配置”和“WebSocket 跨域放行”两方面入手,以下是完整可复用的脱敏配置方案(已替换敏感路径、类名,保留核心逻辑)。

1. 核心:HTTP 跨域凭据配置(解决 CORS 凭据不匹配)

Spring Security 中配置跨域的关键是:明确允许凭据传递、确保 CORS 过滤器优先执行、避免配置冲突

完整 Security 配置代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;/*** 安全配置类:负责权限控制、跨域配置、过滤器管理*/
@Configuration
public class AppSecurityConfig {// 注入自定义组件(脱敏:替换为项目通用命名,避免暴露业务细节)private final AuthFailHandler authFailHandler; // 认证失败处理器private final LogoutHandler appLogoutHandler; // 退出成功处理器private final TokenAuthFilter tokenAuthFilter; // Token认证过滤器private final WhiteListProperties whiteListProps; // 白名单URL配置(从配置文件读取)// 构造函数注入(Lombok @RequiredArgsConstructor 可简化代码)public AppSecurityConfig(AuthFailHandler authFailHandler,LogoutHandler appLogoutHandler,TokenAuthFilter tokenAuthFilter,WhiteListProperties whiteListProps) {this.authFailHandler = authFailHandler;this.appLogoutHandler = appLogoutHandler;this.tokenAuthFilter = tokenAuthFilter;this.whiteListProps = whiteListProps;}/*** 配置Security过滤器链:核心权限与跨域规则*/@Beanprotected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http// 1. 配置CORS:注入自定义跨域配置源(优先级最高).cors(cors -> cors.configurationSource(corsConfigSource()))// 2. 禁用CSRF:前后端分离用Token认证,无需依赖Session.csrf(csrf -> csrf.disable())// 3. 响应头配置:解决X-Frame-Options嵌入限制、缓存问题.headers(headers -> headers.cacheControl(cache -> cache.disable()) // 禁用浏览器缓存敏感资源.frameOptions(options -> options.disable()) // 允许跨域页面嵌入(按需调整))// 4. 异常处理:认证失败时返回统一格式响应.exceptionHandling(ex -> ex.authenticationEntryPoint(authFailHandler))// 5. Session配置:无状态模式(Token认证不需要Session).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 6. 授权规则:白名单路径匿名访问,其余需认证.authorizeHttpRequests(auth -> {// 放行配置文件中的白名单URL(如第三方回调、公开接口)whiteListProps.getUrls().forEach(url -> auth.antMatchers(url).permitAll());// 基础公开接口:登录、注册、验证码auth.antMatchers("/api/auth/login", "/api/auth/register", "/api/auth/captcha").permitAll()// 静态资源:HTML、CSS、JS、图片等.antMatchers(HttpMethod.GET, "/", "/*.html", "/static/**/*.html", "/static/**/*.css", "/static/**/*.js", "/static/**/*.png").permitAll()// 文档资源:Swagger、监控页面(生产环境建议关闭).antMatchers("/swagger-ui/**", "/swagger-resources/**", "/webjars/**", "/v3/api-docs/**", "/monitor/druid/**").permitAll()// WebSocket相关路径:全部放行(含动态子路径).antMatchers("/api/ws/**").permitAll()// 其余所有请求:必须认证(Token有效).anyRequest().authenticated();})// 7. 退出登录配置:指定退出接口与处理器.logout(logout -> logout.logoutUrl("/api/auth/logout").logoutSuccessHandler(appLogoutHandler).clearAuthentication(true) // 清除认证信息)// 8. 注入Token过滤器:在用户名密码过滤器前执行(优先验证Token).addFilterBefore(tokenAuthFilter, UsernamePasswordAuthenticationFilter.class).build();}/*** 自定义CORS配置源:解决跨域凭据问题的核心配置*/@Beanpublic CorsConfigurationSource corsConfigSource() {CorsConfiguration corsConfig = new CorsConfiguration();// 1. 允许的来源:生产环境必须替换为具体前端域名(如"https://app.xxx.com")// 注意:addAllowedOriginPattern支持通配符且兼容凭据模式,addAllowedOrigin不支持corsConfig.addAllowedOriginPattern("*");// 2. 允许的请求头:支持所有自定义头(如Token、语言标识)corsConfig.addAllowedHeader("*");// 3. 允许的请求方法:GET、POST、PUT、DELETE等常用方法corsConfig.addAllowedMethod(HttpMethod.GET);corsConfig.addAllowedMethod(HttpMethod.POST);corsConfig.addAllowedMethod(HttpMethod.PUT);corsConfig.addAllowedMethod(HttpMethod.DELETE);corsConfig.addAllowedMethod(HttpMethod.OPTIONS); // 允许预检请求// 4. 关键配置:允许凭据传递(与前端withCredentials: true匹配)corsConfig.setAllowCredentials(true);// 5. 预检请求有效期:3600秒(减少浏览器重复发送OPTIONS请求)corsConfig.setMaxAge(3600L);// 6. 应用范围:所有接口路径都生效UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();configSource.registerCorsConfiguration("/**", corsConfig);return configSource;}
}

2. 补充:WebSocket 跨域配置(解决连接失败)

若项目使用 WebSocket(如实时通知、消息推送),需单独配置 WebSocket 跨域,确保与 HTTP 跨域规则一致(已脱敏路径与类名):

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;/*** WebSocket配置类:处理实时连接的跨域与路径映射*/
@Configuration
@EnableWebSocket // 启用WebSocket支持
public class AppWebSocketConfig implements WebSocketConfigurer {// 注入自定义WebSocket处理器(脱敏:替换为项目实际处理器)private final AppWebSocketHandler webSocketHandler;public AppWebSocketConfig(AppWebSocketHandler webSocketHandler) {this.webSocketHandler = webSocketHandler;}@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 1. 注册处理器:映射所有/api/ws/**路径的WebSocket请求// 2. 跨域配置:生产环境替换为具体前端域名(如"https://app.xxx.com")// 3. SockJS降级:兼容不支持WebSocket的浏览器(按需启用)registry.addHandler(webSocketHandler, "/api/ws/**").setAllowedOrigins("*").withSockJS();}
}

3. 配套配置类(脱敏:白名单与处理器示例)

为确保配置完整性,补充核心依赖类的脱敏示例(仅展示结构,无业务敏感信息):

(1)白名单配置类(读取配置文件)
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;/*** 白名单配置:从application.yml读取无需认证的URL*/
@Component
@ConfigurationProperties(prefix = "app.security.white-list")
public class WhiteListProperties {// 白名单URL列表(如:/api/callback/thirdparty, /api/public/notice)private List<String> urls;// Getter & Setterpublic List<String> getUrls() {return urls;}public void setUrls(List<String> urls) {this.urls = urls;}
}
(2)认证失败处理器(统一响应格式)
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** 认证失败处理器:返回统一的JSON格式错误响应*/
public class AuthFailHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {// 统一响应格式(脱敏:替换为项目实际响应工具类)response.setContentType("application/json;charset=UTF-8");response.getWriter().write("{\"code\":401,\"msg\":\"认证失败,请重新登录\",\"data\":null}");}
}

三、关键配置解析:避坑要点

很多开发者配置跨域后仍报错,本质是没理解以下核心规则:

1. allowCredentials: true 的强制约束

corsConfig.setAllowCredentials(true) 时,浏览器禁止 Access-Control-Allow-Origin*,因此必须用 addAllowedOriginPattern("*") 替代 addAllowedOrigin("*")(Spring 5.3+ 支持)。

  • 错误用法:corsConfig.addAllowedOrigin("*") → 浏览器拦截响应(安全限制)
  • 正确用法:corsConfig.addAllowedOriginPattern("*") → 兼容凭据模式,支持通配符

2. CORS 过滤器的执行顺序

Spring Security 链中,CORS 过滤器必须优先于认证过滤器(如 Token 过滤器),否则请求会先被认证拦截,导致 CORS 头未返回。
本文通过 .cors(cors -> cors.configurationSource(...)) 配置,Spring 会自动将 CORS 过滤器加入到 Security 链最前端,避免手动 addFilterBefore 导致的顺序冲突。

3. WebSocket 路径的放行规则

WebSocket 请求路径通常包含动态参数(如 /api/ws/123/abc/websocket),因此必须用通配符 /** 放行所有子路径:

  • 错误:.antMatchers("/api/ws", "/api/ws/info").permitAll() → 仅放行固定路径
  • 正确:.antMatchers("/api/ws/**").permitAll() → 放行所有 /api/ws 下的子路径

4. 前端配合配置(脱敏示例)

跨域是前后端协同问题,前端需确保请求启用凭据模式(以 Axios 和 WebSocket 为例):

// 1. Axios HTTP请求(携带Token凭据)
axios({url: "http://127.0.0.1:8080/api/ws/info",method: "GET",withCredentials: true, // 启用凭据传递(必须与后端匹配)headers: {"Authorization": "Bearer " + token // 示例:Token放在请求头}
});// 2. WebSocket连接(实时通信)
const ws = new WebSocket("ws://127.0.0.1:8080/api/ws/123/abc/websocket");
// 若用SockJS降级(兼容低版本浏览器)
const sock = new SockJS("http://127.0.0.1:8080/api/ws", null, {withCredentials: true
});

四、验证与排错

配置完成后,可通过浏览器“Network”面板验证,或使用 Postman 模拟跨域请求:

1. 验证核心响应头

成功请求应包含以下头信息(可在“Response Headers”中查看):

Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: 前端实际域名(如 http://localhost:8081)
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Max-Age: 3600

2. 常见错误排错步骤

错误现象可能原因解决方案
403 Forbidden1. 目标路径未加入白名单
2. Token过滤器拦截了WebSocket请求
1. 检查 authorizeHttpRequests 中是否放行路径
2. 在Token过滤器中排除WebSocket路径(如 /api/ws/**
WebSocket连接失败1. WebSocket跨域未配置
2. 路径未放行
1. 检查 AppWebSocketConfigsetAllowedOrigins 配置
2. 确认 .antMatchers("/api/ws/**").permitAll() 已添加
X-Frame-Options错误frameOptions 配置为 sameOriginheaders.frameOptions(options -> options.disable()) 启用

五、生产环境优化建议

  1. 限制允许的来源:将 addAllowedOriginPattern("*") 替换为具体前端域名(如 https://app.xxx.com),避免任意域名跨域,降低安全风险。
  2. 缩小请求方法范围:若接口仅支持 GET/POST,可删除 PUT/DELETE 方法配置,减少攻击面(如 corsConfig.addAllowedMethod(HttpMethod.GET))。
  3. 关闭不必要功能:生产环境建议关闭 Swagger、Druid 等文档/监控页面,或通过 IP 白名单限制访问。
  4. 日志排查:开启 Security debug 日志(logging.level.org.springframework.security=DEBUG),快速定位拦截原因(生产环境需关闭)。

总结

跨域问题的核心是“前后端配置匹配”,尤其是凭据模式下,需确保:

  • 后端返回 Access-Control-Allow-Credentials: true 响应头
  • 允许的来源用 addAllowedOriginPattern 而非 addAllowedOrigin
  • WebSocket 路径单独放行并配置跨域
  • CORS 过滤器在 Security 链中优先执行

按照本文脱敏配置,可彻底解决 CORS 凭据不匹配、WebSocket 连接失败等问题,同时兼顾安全性与灵活性,适配大多数前后端分离项目场景。

http://www.dtcms.com/a/434380.html

相关文章:

  • Tabby下载安装与连接服务器
  • Apache Beam入门教程:统一批流处理模型
  • 计算机毕业设计 基于Hadoop的信贷风险评估的数据可视化分析与预测系统 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
  • 【QT常用技术讲解】QTablewidget单元格存储隐藏的数据
  • K8s学习笔记(九) job与cronjob
  • MATLAB线性代数函数完全指南
  • 关于单片机外设存储芯片的应用笔记(IIC驱动)
  • 梅州网站建设南宁网站 制作
  • 2015 年真题配套词汇单词笔记(考研真相)
  • 中国建设银行舟山分行网站网站构建的过程
  • python如何通过链接下载保存视频
  • K-Lite Mega/FULL Codec Pack(视频解码器)
  • SpringBoot+Vue医院预约挂号系统 附带详细运行指导视频
  • 85-dify案例分享-不用等 OpenAI 邀请,Dify+Sora2工作流实测:写实动漫视频随手做,插件+教程全送
  • GUI高级工程师面试题
  • 经典网站设计风格网站建设产品介绍
  • 基于单片机的人体心率、体温监测系统(论文+源码)
  • WinScp下载与安装
  • 普中stm32大Dap烧录流程
  • 宝安附近做网站公司网站做好了前端 后端怎么做
  • 新媒体营销h5制作网站中国水土保持生态建设网站
  • ubuntu 服务器(带NVLink)更新显卡驱动 (巨坑!!)
  • jQuery提供了多种选择器,可以快速获取DOM元素
  • 【LaTeX】 6 LaTeX 扩展功能
  • 软件测试基础-03(缺陷)
  • 重庆建设公司网站做网站的工作好吗
  • GitHub 热榜项目 - 日榜(2025-10-02)
  • PEFT实战LoRA微调OpenAI Whisper 中文语音识别
  • Django第三方扩展详解:提升开发效率的利器
  • 正能量不良网站直接进入自助建站系统模板