Sa-Token全局过滤器解决跨域问题的3种方式
1. 为什么需要处理跨域问题跨域问题源于浏览器的同源策略Same-Origin Policy这是现代浏览器最基本的安全机制之一。同源策略要求网页只能访问与其来源相同的资源这里的同源指的是协议、域名和端口号完全相同。当这三个要素中有任何一个不同时浏览器就会阻止跨域请求。在实际开发中前后端分离的架构越来越普遍。前端可能运行在http://localhost:3000而后端API服务运行在http://localhost:8080即使域名相同端口不同也会触发跨域限制。常见的跨域场景包括不同子域名之间的访问如a.example.com访问b.example.com不同协议之间的访问如https访问http不同端口之间的访问如8080访问3000完全不同的域名之间的访问跨域请求被浏览器拦截时通常会看到类似Access-Control-Allow-Origin的错误提示。这不仅影响开发调试也会导致生产环境的功能异常。2. Sa-Token全局过滤器简介Sa-Token是一个轻量级的Java权限认证框架它提供了一套简单易用的API来处理认证、授权、会话管理等常见需求。其中全局过滤器是Sa-Token的一个重要特性它允许开发者在请求进入业务逻辑前或返回响应前进行统一处理。全局过滤器的核心优势在于统一入口所有请求都会经过过滤器无需在每个Controller中重复处理灵活配置可以针对特定路径进行过滤也可以全局生效功能丰富除了跨域处理还可以实现权限校验、请求日志、参数预处理等功能在Sa-Token中全局过滤器通过实现SaFilter接口或继承SaFilterAdaptor类来创建。过滤器可以定义多个并通过Order注解指定执行顺序。3. 第一种方式基础CORS配置3.1 实现原理CORSCross-Origin Resource Sharing是W3C标准它通过服务器端设置特定的HTTP头来告诉浏览器哪些跨域请求是被允许的。Sa-Token的全局过滤器可以方便地添加这些响应头。3.2 具体实现创建一个全局过滤器类并注册到Spring容器Configuration public class SaTokenConfigure { Bean public SaFilter getSaFilter() { return new SaFilter() // 指定拦截路径 .addInclude(/**) // 排除路径 .addExclude(/favicon.ico) // 认证函数 .setAuth(obj - {}) // 异常处理函数 .setError(e - SaResult.error(e.getMessage())) // 前置函数 .setBeforeAuth(r - { // 设置CORS头 SaHolder.getResponse() // 允许的源 .setHeader(Access-Control-Allow-Origin, *) // 允许的请求方法 .setHeader(Access-Control-Allow-Methods, POST, GET, OPTIONS, DELETE) // 预检请求缓存时间 .setHeader(Access-Control-Max-Age, 3600) // 允许的请求头 .setHeader(Access-Control-Allow-Headers, x-requested-with, sa-token, Content-Type); // 如果是OPTIONS请求则立即返回 if (SaHolder.getRequest().getMethod().equals(OPTIONS)) { SaRouter.back(); } }); } }3.3 注意事项Access-Control-Allow-Origin设置为*表示允许所有源访问在生产环境中建议指定具体的域名OPTIONS请求是CORS预检请求需要特殊处理如果请求中需要携带认证信息如Cookie则Access-Control-Allow-Origin不能为*且需要设置Access-Control-Allow-Credentials: true4. 第二种方式基于注解的细粒度控制4.1 实现原理Sa-Token提供了SaCheckCors注解可以在Controller类或方法上使用实现更细粒度的跨域控制。这种方式适合需要针对不同接口设置不同跨域规则的场景。4.2 具体实现首先创建一个注解处理器Component public class SaCheckCorsInterceptor implements SaInterceptor { Override public void preHandle(Object handler) { // 获取方法或类上的SaCheckCors注解 SaCheckCors saCheckCors SaAnnotationUtil.getAnnotation(handler, SaCheckCors.class); if (saCheckCors ! null) { // 设置CORS头 SaHolder.getResponse() .setHeader(Access-Control-Allow-Origin, saCheckCors.origin()) .setHeader(Access-Control-Allow-Methods, saCheckCors.methods()) .setHeader(Access-Control-Allow-Headers, saCheckCors.headers()); // 处理OPTIONS请求 if (OPTIONS.equals(SaHolder.getRequest().getMethod())) { SaRouter.back(); } } } }然后在Controller中使用注解RestController RequestMapping(/api) public class ApiController { SaCheckCors(origin https://example.com, methods GET,POST, headers Content-Type,sa-token) GetMapping(/data) public SaResult getData() { return SaResult.data(some data); } SaCheckCors(origin *, methods GET) GetMapping(/public) public SaResult getPublicData() { return SaResult.data(public data); } }4.3 注意事项注解方式适合需要差异化配置的场景如果所有接口规则相同建议使用全局配置注解处理器需要在Sa-Token配置中注册方法级别的注解会覆盖类级别的注解5. 第三种方式结合Spring的CorsRegistry5.1 实现原理Spring框架本身提供了跨域支持的机制可以通过WebMvcConfigurer接口的addCorsMappings方法配置。Sa-Token可以与这种机制无缝集成。5.2 具体实现创建一个配置类同时配置Sa-Token和CORSConfiguration public class WebMvcConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { // 配置Spring CORS registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .maxAge(3600); } Bean public SaFilter getSaFilter() { return new SaFilter() .addInclude(/**) .setAuth(obj - {}) // 其他Sa-Token配置 ; } }5.3 注意事项Spring的CORS配置和Sa-Token的过滤器可以同时使用但要注意不要重复设置响应头这种方式适合已经熟悉Spring CORS配置的开发者在Spring Boot 2.4版本中可能需要额外配置以允许凭证credentials6. 三种方式的对比与选型建议6.1 功能对比方式配置位置细粒度控制复杂度适用场景基础CORS配置全局过滤器低低简单项目统一规则基于注解方法/类级别高中需要差异化配置的项目结合Spring CORS配置类中低已使用Spring CORS的项目6.2 性能考虑全局过滤器方式对每个请求都会执行CORS头设置有一定性能开销注解方式只对标记的接口生效性能更好但配置更复杂Spring CORS方式在框架层面处理性能通常最优6.3 安全建议生产环境不要使用Access-Control-Allow-Origin: *应明确指定允许的域名对于敏感接口建议结合Sa-Token的认证功能进行保护注意防范CSRF攻击即使解决了跨域问题7. 常见问题排查7.1 预检请求(OPTIONS)处理不当现象前端发送POST请求时浏览器先发送OPTIONS请求但后端返回404或405。解决方案确保全局过滤器正确处理OPTIONS请求在Spring Security配置中允许OPTIONS请求Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers(HttpMethod.OPTIONS).permitAll() // 其他配置 ; }7.2 凭证(Credentials)问题现象前端设置了withCredentials: true但请求失败。解决方案后端响应头需要包含Access-Control-Allow-Credentials: trueAccess-Control-Allow-Origin不能为*必须指定具体域名SaHolder.getResponse() .setHeader(Access-Control-Allow-Origin, https://yourdomain.com) .setHeader(Access-Control-Allow-Credentials, true);7.3 响应头缺失现象某些自定义头信息在前端无法获取。解决方案确保响应头包含Access-Control-Expose-Headers在Sa-Token过滤器中添加配置.setHeader(Access-Control-Expose-Headers, custom-header1, custom-header2);8. 高级应用场景8.1 多环境差异化配置在实际项目中开发、测试、生产环境的域名通常不同。可以通过Spring的Profile机制实现环境隔离Profile(dev) Bean public SaFilter devSaFilter() { return new SaFilter() .setBeforeAuth(r - { SaHolder.getResponse() .setHeader(Access-Control-Allow-Origin, http://localhost:3000); }); } Profile(prod) Bean public SaFilter prodSaFilter() { return new SaFilter() .setBeforeAuth(r - { SaHolder.getResponse() .setHeader(Access-Control-Allow-Origin, https://production.com); }); }8.2 动态域名白名单对于SaaS类应用可能需要根据请求动态判断是否允许跨域.setBeforeAuth(r - { String origin SaHolder.getRequest().getHeader(Origin); if (isAllowedOrigin(origin)) { // 自定义校验逻辑 SaHolder.getResponse() .setHeader(Access-Control-Allow-Origin, origin) .setHeader(Access-Control-Allow-Credentials, true); } });8.3 结合Sa-Token认证跨域请求通常需要携带认证信息可以与Sa-Token的登录认证结合.setAuth(obj - { // 执行认证逻辑 SaRouter.match(/**, () - StpUtil.checkLogin()); }).setBeforeAuth(r - { // 设置CORS头 SaHolder.getResponse() .setHeader(Access-Control-Allow-Origin, https://yourdomain.com) .setHeader(Access-Control-Allow-Credentials, true); });9. 性能优化建议减少不必要的头信息只添加必要的CORS头避免过度配置合理设置Max-Age对于稳定的API可以设置较长的预检缓存时间避免重复处理确保每个请求只经过一次CORS处理考虑Nginx层处理对于高并发场景可以在Nginx中处理CORS减轻应用服务器压力location / { add_header Access-Control-Allow-Origin https://yourdomain.com; add_header Access-Control-Allow-Methods GET, POST, OPTIONS; add_header Access-Control-Allow-Headers DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range; add_header Access-Control-Expose-Headers Content-Length,Content-Range; if ($request_method OPTIONS) { add_header Access-Control-Max-Age 1728000; add_header Content-Type text/plain; charsetutf-8; add_header Content-Length 0; return 204; } }10. 实际项目中的经验分享在实际项目中使用Sa-Token处理跨域问题时有几个值得注意的经验点开发环境与生产环境的差异开发环境通常需要允许所有源*方便调试但生产环境必须严格限制。可以使用环境变量来区分String allowedOrigin prod.equals(env) ? https://production.com : *; SaHolder.getResponse().setHeader(Access-Control-Allow-Origin, allowedOrigin);移动端特殊处理某些移动端浏览器对CORS的实现可能有差异特别是混合应用Hybrid App中。遇到问题时可以尝试确保服务器正确响应OPTIONS请求检查是否有重定向导致CORS头丢失使用工具抓包分析请求和响应头缓存问题浏览器会缓存CORS响应特别是预检请求。修改CORS配置后可能需要清除浏览器缓存或使用隐身模式测试。监控与日志建议记录被拒绝的跨域请求便于排查问题.setBeforeAuth(r - { String origin SaHolder.getRequest().getHeader(Origin); if (!isAllowedOrigin(origin)) { log.warn(Blocked CORS request from origin: {}, origin); } });安全加固除了CORS外还应该考虑CSRF防护请求频率限制敏感操作二次验证在最近的一个电商平台项目中我们采用了注解方式结合环境配置的方案。开发阶段使用宽松的跨域策略方便前后端并行开发预发布环境开始启用严格的域名白名单生产环境则进一步限制允许的HTTP方法和头信息。这种渐进式的安全策略既保证了开发效率又确保了生产环境的安全性。