ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Spring CORS Filter实现与跨域问题解决方案

Spring CORS Filter实现与跨域问题解决方案 1. Spring跨域CORS Filter详解跨域资源共享(CORS)是现代Web开发中不可避免的问题。当你的前端应用运行在http://localhost:8080而后端API部署在http://api.example.com时浏览器会阻止这种跨域请求。Spring框架提供了多种方式来解决这个问题其中Filter方案是最灵活、可控性最强的一种。1.1 为什么需要CORS处理浏览器出于安全考虑使用同源策略(Same-Origin Policy)来限制不同源之间的资源交互。同源指的是协议、域名和端口完全相同。但在实际项目中前后端分离的架构非常普遍这就导致了跨域问题。常见的CORS错误包括No Access-Control-Allow-Origin header is present on the requested resourceResponse to preflight request doesnt pass access control check1.2 Spring中的CORS解决方案对比Spring提供了几种处理CORS的方式CrossOrigin注解最简单的方式直接在Controller或方法上添加注解全局CORS配置通过WebMvcConfigurer配置CORS Filter最灵活的方式可以精细控制每个请求的CORS行为选择Filter方案的主要原因是可以统一处理所有请求避免在每个Controller上重复注解可以自定义复杂的CORS逻辑如动态判断允许的源可以与其他Filter配合在请求处理的早期阶段解决跨域问题2. 实现Spring CORS Filter2.1 基础CORS Filter实现下面是一个基础的CORS Filter实现public class SimpleCorsFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) res; HttpServletRequest request (HttpServletRequest) req; // 允许所有域名访问 response.setHeader(Access-Control-Allow-Origin, *); // 允许的HTTP方法 response.setHeader(Access-Control-Allow-Methods, POST, GET, OPTIONS, DELETE, PUT); // 预检请求缓存时间(秒) response.setHeader(Access-Control-Max-Age, 3600); // 允许的请求头 response.setHeader(Access-Control-Allow-Headers, x-requested-with, Content-Type, Authorization); // 允许浏览器在跨域请求中携带凭证(cookie等) // response.setHeader(Access-Control-Allow-Credentials, true); if (OPTIONS.equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, res); } } Override public void init(FilterConfig filterConfig) {} Override public void destroy() {} }2.2 注册CORS Filter在Spring Boot应用中注册Filter有多种方式方式1使用Bean注册Configuration public class FilterConfig { Bean public FilterRegistrationBeanSimpleCorsFilter corsFilter() { FilterRegistrationBeanSimpleCorsFilter registrationBean new FilterRegistrationBean(); registrationBean.setFilter(new SimpleCorsFilter()); registrationBean.addUrlPatterns(/*); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 设置高优先级 return registrationBean; } }方式2使用ServletComponentScanWebFilter(urlPatterns /*) public class SimpleCorsFilter implements Filter { // 同上 } // 然后在启动类上添加 ServletComponentScan SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }2.3 动态配置允许的源生产环境中我们通常不会允许所有源(*)而是动态配置允许的域名public class DynamicCorsFilter implements Filter { private ListString allowedOrigins Arrays.asList( http://localhost:8080, https://example.com ); Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) res; HttpServletRequest request (HttpServletRequest) req; String origin request.getHeader(Origin); if (allowedOrigins.contains(origin)) { response.setHeader(Access-Control-Allow-Origin, origin); response.setHeader(Vary, Origin); // 告诉浏览器响应会根据Origin变化 } // 其他CORS头设置... if (OPTIONS.equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, res); } } }3. 高级CORS配置3.1 处理预检请求(Preflight Request)对于非简单请求(如Content-Type为application/json的POST请求)浏览器会先发送OPTIONS方法的预检请求。我们的Filter需要正确处理这类请求Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) res; HttpServletRequest request (HttpServletRequest) req; // 设置CORS头 response.setHeader(Access-Control-Allow-Origin, http://trusted-domain.com); response.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); response.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); response.setHeader(Access-Control-Max-Age, 3600); // 如果是OPTIONS请求直接返回200 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); return; // 注意这里要return不再继续执行Filter链 } chain.doFilter(req, res); }3.2 支持Credentials当需要跨域传递cookie或认证信息时需要额外配置response.setHeader(Access-Control-Allow-Credentials, true);但要注意不能同时使用Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true前端请求需要设置withCredentials: true(在axios中是axios.defaults.withCredentials true)3.3 自定义CORS策略对于更复杂的场景可以实现自定义的CORS策略public class CustomCorsFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response (HttpServletResponse) res; HttpServletRequest request (HttpServletRequest) req; // 从数据库或配置中心动态获取允许的源 ListString allowedOrigins loadAllowedOriginsFromDB(); String origin request.getHeader(Origin); if (allowedOrigins.contains(origin)) { response.setHeader(Access-Control-Allow-Origin, origin); response.setHeader(Access-Control-Allow-Methods, determineAllowedMethods(request)); response.setHeader(Access-Control-Allow-Headers, determineAllowedHeaders(request)); response.setHeader(Access-Control-Max-Age, 3600); if (needCredentials(request)) { response.setHeader(Access-Control-Allow-Credentials, true); } } if (OPTIONS.equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, res); } } // 其他辅助方法... }4. 常见问题与解决方案4.1 CORS问题排查清单当遇到CORS问题时可以按照以下步骤排查检查响应头确保服务器返回了正确的CORS头Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers检查请求类型简单请求GET/HEAD/POST且Content-Type为text/plain、multipart/form-data或application/x-www-form-urlencoded非简单请求会触发预检请求(OPTIONS)检查预检请求处理服务器是否正确响应了OPTIONS请求Access-Control-Max-Age是否设置合理检查凭证设置如果需要cookie确保没有使用Access-Control-Allow-Origin: *前后端都要设置withCredentials4.2 常见错误及解决问题1Response to preflight request doesnt pass access control check原因预检请求没有返回正确的CORS头解决确保OPTIONS请求也经过CORS Filter处理并返回正确的头信息问题2The Access-Control-Allow-Origin header contains multiple values *, *原因可能有多处配置了CORS(如同时使用了Filter和CrossOrigin)解决统一CORS配置方式避免重复设置问题3Request header field Authorization is not allowed by Access-Control-Allow-Headers原因请求头中包含自定义头(如Authorization)但服务器没有允许解决在Filter中添加该头到Access-Control-Allow-Headers4.3 性能优化建议合理设置Access-Control-Max-Age对于稳定的API可以设置较长的缓存时间(如3600秒)对于频繁变更的API可以设置较短时间或禁用缓存使用Vary头当动态设置Access-Control-Allow-Origin时添加Vary: Origin头这可以避免浏览器缓存错误的CORS响应避免过度开放的配置生产环境不要使用Access-Control-Allow-Origin: *根据实际需要最小化允许的方法和头5. Spring Security中的CORS如果你的应用使用了Spring Security需要注意CORS Filter的执行顺序Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .cors().and() // 启用Spring Security的CORS支持 .csrf().disable() .authorizeRequests() // 其他配置... } // 配置CORS源 Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(http://trusted-domain.com)); configuration.setAllowedMethods(Arrays.asList(GET, POST, PUT, DELETE)); configuration.setAllowedHeaders(Arrays.asList(Content-Type, Authorization)); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, configuration); return source; } }关键点确保调用了.cors().and()CORS Filter需要在Spring Security过滤器之前执行可以通过corsConfigurationSource()方法配置CORS规则6. 测试CORS配置6.1 使用curl测试测试普通请求curl -H Origin: http://example.com \ -H Access-Control-Request-Method: POST \ -H Access-Control-Request-Headers: Content-Type \ -X OPTIONS --verbose http://your-api.com/endpoint检查返回头中是否包含正确的CORS头。6.2 使用Postman测试虽然Postman不会强制执行CORS策略但可以用来检查OPTIONS请求的响应验证服务器是否正确设置了CORS头6.3 浏览器端测试编写简单HTML页面测试跨域请求!DOCTYPE html html head titleCORS Test/title script srchttps://unpkg.com/axios/dist/axios.min.js/script /head body button onclicktestCors()Test CORS/button script function testCors() { axios.get(http://your-api.com/endpoint, { headers: { Content-Type: application/json } }) .then(response console.log(response)) .catch(error console.error(error)); } /script /body /html7. 生产环境最佳实践白名单管理将允许的域名存储在数据库或配置中心实现动态更新白名单的机制监控与告警监控被拒绝的CORS请求设置告警当未知域名频繁尝试访问时日志记录记录CORS相关的决策过程特别是记录被拒绝的请求及其来源与API网关集成在API网关层统一处理CORS减少应用层的处理负担安全加固定期审查允许的源和方法避免过度开放的配置8. 替代方案比较虽然Filter方案很灵活但Spring还提供了其他CORS解决方案CrossOrigin注解优点简单直接标注在Controller上缺点分散在各处难以统一管理WebMvcConfigurer全局配置Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://trusted-domain.com) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true); } }优点配置简单Spring Boot风格缺点灵活性不如FilterAPI网关处理在Nginx、Spring Cloud Gateway等网关层处理CORS优点统一处理减轻应用负担缺点需要额外基础设施选择建议简单应用使用CrossOrigin或WebMvcConfigurer复杂需求使用CORS Filter微服务架构在API网关统一处理9. 源码解析Spring如何处理CORS理解Spring底层CORS处理机制有助于更好地使用和调试CorsFilterSpring提供的默认CORS Filter实现CorsConfiguration封装CORS配置信息CorsProcessor处理CORS请求的核心接口DefaultCorsProcessor默认实现处理预检请求和简单请求关键处理流程检查请求是否CORS请求(有Origin头)查找匹配的CorsConfiguration验证请求的Origin、Method、Headers是否被允许设置响应头或拒绝请求10. 总结与个人实践建议在实际项目中使用CORS Filter时我总结了以下几点经验统一配置优于分散配置尽量在一个地方管理所有CORS规则避免在多个地方重复配置环境区分开发环境可以宽松配置(如允许所有源)但生产环境必须严格限制动态配置将允许的源存储在数据库或配置中心支持动态更新而无需重启应用监控记录所有被拒绝的CORS请求用于安全分析和故障排查文档为团队维护CORS配置文档说明每个配置项的作用和安全考虑测试为CORS逻辑编写单元测试和集成测试确保配置变更不会意外破坏现有功能性能对于高频API合理设置Access-Control-Max-Age减少预检请求安全定期审查CORS配置确保没有过度开放的权限最后记住CORS只是浏览器端的限制不是服务器端的限制。确保你的服务器端API本身也有适当的安全措施不要依赖CORS作为唯一的安全防线。
返回列表