新闻详情

新闻详情

首页 / 资讯中心 / 详情

CORS跨域原理与Spring Boot、Nginx等主流框架解决方案详解

发布时间:2026/10/1 11:42:37来源:尧图网络
CORS跨域原理与Spring Boot、Nginx等主流框架解决方案详解
先说说这个报错吧。我在实际项目里几乎每周都能撞见一次尤其是当前端页面在浏览器里调用后端接口控制台冒出那句经典的No Access-Control-Allow-Origin header is present on the requested resource.紧接着就是另一个英文提示has been blocked by CORS policy。很多新手第一次遇到直接懵了以为是后端崩了或者接口地址写错了其实都不是。CORS跨域资源共享问题是 Web 开发里的老熟人前端、后端、运维都可能被它卡一道。我自己从没真正理解它的时候就靠改后端代码瞎试到现在能快速定位是浏览器的锅、后端的锅还是 Nginx 的锅中间踩了不少坑。今天这篇我就把 CORS 从原理到几种主流框架的解决方案一次说清楚争取让你看完就能解决自己的问题。如果你是刚入行没多久的前端、后端或者一个人扛全栈项目又或者公司接口由别人提供你只是负责页面联调这篇内容都适合你。我不绕弯子直接开门见山。1. 先搞清楚 CORS 到底是什么以及它为什么会拦你1.1 同源策略是浏览器定的规矩不是服务器在拦你很多人在处理 CORS 问题时搞错了一个方向以为是后端拒绝了你。其实不是真正“拦路”的是浏览器。浏览器在 Web 安全模型里有一个核心机制叫同源策略。所谓同源指的是协议、域名、端口三个都要一致。举个最简单的例子你的前端页面跑在http://localhost:8080接口地址是http://localhost:3000/api虽然都是 localhost但端口不同这就不是同源。此时浏览器发起跨域请求服务器其实大概率正常处理了请求返回了数据但浏览器拿到响应后一检查发现响应头里没有Access-Control-Allow-Origin这个字段而且当前页面的源不在允许名单里于是浏览器直接把这个响应“吞了”并在控制台抛出一条 CORS 错误。所以记住最关键的一点CORS 是浏览器层面的安全限制服务器可能已经正常工作只是浏览器不把结果交给你。1.2 简单请求和预检请求两条不同的处理路径CORS 请求又分两种情况简单请求和预检请求。简单请求需要同时满足几个条件比如请求方法是GET、POST、HEAD之一请求头只包含常规字段比如Content-Type: text/plain这类安全字段没有自定义头没有Authorization等。这种请求浏览器会直接发出去只在响应阶段校验 CORS 响应头。但如果你的请求携带了自定义头比如Authorization、X-Requested-With或者Content-Type是application/json又或者使用了PUT、DELETE等方法浏览器就会先发送一个OPTIONS 预检请求Preflight Request询问服务器“我这个跨域请求允许吗允许哪些方法允许哪些头”服务器必须对这个 OPTIONS 请求返回正确的 CORS 响应头浏览器才会真正发出后续的实际请求。那段经典的报错里还有一层含义预检请求失败了。很多后端新手只给业务接口加了 CORS 响应头却忽略了 OPTIONS 请求的处理结果就是明明“加了跨域配置”却依然报错。这一点后面讲具体方案时会反复提到。2. 后端几种主流框架的跨域配置方案2.1 Spring Boot 的三种写法建议按场景选先说我日常做得最多的 Java 后端。Spring Boot 处理 CORS 的方式非常多我按从简到繁的顺序梳理。方式一注解式适合单个接口或单个 Controller在方法或类上直接加CrossOrigin注解RestController RequestMapping(/api/user) public class UserController { CrossOrigin(origins http://localhost:8080) GetMapping(/info) public UserInfo getInfo() { return userService.getInfo(); } }优点是很直观、零配置缺点是如果接口很多每个接口都加注解很繁琐而且写死在代码里后期换环境就得改代码。方式二全局配置类适合统一控制这种是我在项目里最常用的。定义一个WebMvcConfigurer的实现类重写addCorsMappings方法Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8080, https://admin.example.com) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }这里面有几个参数我需要重点解释一下因为它们在调参时太容易踩坑allowedOrigins明确允许哪些来源访问。生产环境不要用*尤其是当你还需要携带 Cookie 的时候。allowCredentials(true)允许携带认证信息。这里有个坑如果allowCredentials为 true那么allowedOrigins就不能写*这是协议层面的硬性规定浏览器也会直接报错。maxAge(3600)预检请求的结果可以缓存 3600 秒。这个值建议设置得大一些能显著减少 OPTIONS 请求的次数提升性能。allowedHeaders(*)允许所有请求头。如果你自定义了某些头却又没在这里放开预检请求会失败。方式三Spring Security 场景下的跨域配置如果你的项目整合了 Spring Security光靠上面的配置往往不够因为安全过滤器链的执行顺序在 CORS 配置之前请求可能在进到DispatcherServlet之前就被拦截了。我的做法是在 Security 配置里手动开启 CORS并把它放在过滤器链靠前的位置Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors().and() .csrf().disable() .authorizeHttpRequests(...); return http.build(); } }其中.cors()会去找容器里的CorsConfigurationSource所以要配合定义这个 BeanBean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config new CorsConfiguration(); config.setAllowedOrigins(Arrays.asList(http://localhost:8080)); config.setAllowedMethods(Arrays.asList(GET, POST, PUT, DELETE, OPTIONS)); config.setAllowedHeaders(Arrays.asList(*)); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return source; }如果你用的是 Spring Security一定优先检查是不是安全策略把 OPTIONS 请求或者预检请求拦了。我记得有次在客户环境联调CORS 配置明明写得好好的接口就是不通。查了半天发现是 Spring Security 的authorizeHttpRequests里没有放行 OPTIONS 方法导致预检请求被 401 拦截了。2.2 Node.js Express 里的简单做法与中间件原理Express 里最偷懒也最常用的方式是引入cors中间件npm install cors然后在入口文件里const cors require(cors); app.use(cors({ origin: http://localhost:8080, methods: [GET, POST, PUT, DELETE, OPTIONS], allowedHeaders: [Content-Type, Authorization], credentials: true, maxAge: 3600 }));这个中间件的本质是帮你自动设置响应头。如果你不想引入依赖自己写一个中间件也完全可行app.use((req, res, next) { res.setHeader(Access-Control-Allow-Origin, http://localhost:8080); res.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); res.setHeader(Access-Control-Allow-Headers, Content-Type, Authorization); res.setHeader(Access-Control-Allow-Credentials, true); res.setHeader(Access-Control-Max-Age, 3600); if (req.method OPTIONS) { return res.sendStatus(204); } next(); });注意那个if (req.method OPTIONS)判断。预检请求到达服务器之后如果你不直接返回 204而是正常走了后续路由很多情况下也能通但稳妥的做法是遇到 OPTIONS 请求直接短路掉别再往后走业务逻辑。Express 这块还有个常见痛点如果服务前面挂了 Nginx且 Nginx 没有正确处理 OPTIONS 请求那么 Express 这边逻辑再对也白搭。这个等会儿讲 Nginx 时重点说。2.3 FastAPIPython里 CORS 的配置与常见误区FastAPI 的 CORS 配置非常简洁主要用现成的中间件。我把两种方式都列一下。方式一使用内置 CORSMiddlewarefrom fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app FastAPI() app.add_middleware( CORSMiddleware, allow_origins[http://localhost:8080], allow_methods[*], allow_headers[*], allow_credentialsTrue, max_age3600, ) app.get(/api/user/info) async def get_user_info(): return {username: 张三}这里有个非常值得注意的坑FastAPI 的allow_origins如果设置了具体的域名列表allow_credentials才能安全地设为 true。但是如果你直接用allow_origins[*]并且allow_credentialsTrue在某些版本下会有兼容问题浏览器照样会报错。最好明确写成具体的域名清单。方式二在 Nginx 层统一处理如果你的 FastAPI 应用跑在 Docker 容器里或者需要统一给多个服务配置跨域那么我更推荐用反向代理解决。因为后端代码里写的 CORS 配置只对当前服务生效一旦你有多个微服务每个服务都要写一遍。而 Nginx 作为统一入口加一次就够了。后面我会专门开一节讲 Nginx 的方案。3. 前端处理和 Nginx 层面的兜底方案3.1 开发环境的 proxy 方案别让自己被 CORS 卡住很多前端同学在开发环境就被 CORS 卡死了其实开发阶段有一种更优雅的方案通过构建工具代理转发。以 Vite 为例在vite.config.js里配置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } } });配置好之后前端页面里请求的地址直接写/api/...浏览器看到的请求是同源的根本不会触发 CORS。Vite 开发服务器在中间做了一层代理由它去转发请求到后端。webpack 配置类似在devServer.proxy里写devServer: { proxy: { /api: { target: http://localhost:3000, changeOrigin: true } } }为什么我在开发环境推荐用代理而不是让后端把 CORS 打开原因有几个代理后前后端联调完全不用关心跨域只需要保证后端接口本身可用。生产环境如果前后端部署在同域开发环境的代理逻辑更接近生产实际情况避免“开发环境好好的上线就跨域”。安全问题开发环境的 CORS 配置容易写成允许*如果被带到生产等于把接口裸奔得不偿失。等到联调阶段确确实实需要跨域访问比如前后端分属不同域名但后端你又改不了这时再去靠 Nginx 或者后端 CORS 配置解决。3.2 Nginx 统一网关配置这是我最后兜底的选择生产环境的跨域问题我强烈建议优先在 Nginx 层解决。原因很简单统一。你只需要在 Nginx 配置里加上几行代码后端代码完全不用动对所有后端语言都适用。下面是一份我实际使用过的 Nginx 跨域配置server { listen 80; server_name api.example.com; location / { if ($request_method OPTIONS) { add_header Access-Control-Allow-Origin https://admin.example.com; add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS; add_header Access-Control-Allow-Headers Content-Type, Authorization; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Max-Age 3600; return 204; } add_header Access-Control-Allow-Origin https://admin.example.com; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Expose-Headers Content-Disposition; proxy_pass http://backend_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有个细节Access-Control-Allow-Origin的值通常只能是一个明确的来源。如果前端有多个来源比如既有管理后台admin.example.com又有 H5 端m.example.com在 Nginx 里就不能简单地写死一个。我在项目里用过两种做法一种是 Nginx 用map配合$http_origin变量做动态判断map $http_origin $cors_origin { default ; https://admin.example.com https://admin.example.com; https://m.example.com https://m.example.com; } server { listen 80; server_name api.example.com; location / { if ($request_method OPTIONS) { add_header Access-Control-Allow-Origin $cors_origin; # ... return 204; } add_header Access-Control-Allow-Origin $cors_origin; add_header Access-Control-Allow-Credentials true; proxy_pass http://backend_server; } }另一种是我个人更喜欢的做法直接把 CORS 相关的响应头放到后端的代理响应之后由后端代码里根据请求头动态返回。这样一来 Nginx 层只负责转发跨域逻辑全在后端掌控更灵活只是需要后端多写点代码。Nginx 层还有一个高频坑add_header 的继承问题。如果当前location块没有 add_headerNginx 会继承上一层的 add_header但一旦你写了任意一个 add_header本层所有 add_header 都需要自己写全不会自动继承外层。所以有时候你在 server 块里设置了响应头location 块里又加了一个其他的 add_header结果之前设置的跨域头就“消失”了。这种问题排查起来非常隐蔽。4. 那些年让人崩溃的 CORS 排查场景4.1 带 Cookie 的跨域请求为什么总是失败我可以负责任地说CORS 问题里最容易让前后端互相甩锅的就是 Cookie 相关。典型的报错是Response to preflight request doesnt pass access control check: The value of the Access-Control-Allow-Origin header in the response must not be the wildcard * when the requests credentials mode is include.翻译过来就是当你用fetch或XMLHttpRequest的withCredentials发送跨域请求时服务端返回的Access-Control-Allow-Origin不能是*必须是具体的来源。前端这边fetch 需要设置credentials: includefetch(http://localhost:3000/api/user/info, { method: GET, credentials: include });axios 则是axios.get(http://localhost:3000/api/user/info, { withCredentials: true });前端设置了携带 Cookie后端就必须配合把Access-Control-Allow-Origin设置为请求方的确切源并且Access-Control-Allow-Credentials: true。这三者缺一不可否则请求就失败。我在实际项目里遇到过一个诡异的情况前端 cookie 带了、后端也配置了。但依然失败后来发现是后端allowedOrigins里写的是localhost:8080而浏览器地址栏访问的域名实际是127.0.0.1:8080。localhost和127.0.0.1在 CORS 判断里是两个不同的源这种细节特别坑。所以排查时第一步就是打开浏览器的 Network 面板看请求头里的Origin字段值具体是什么再和后端的allowedOrigins对比。不要凭感觉。4.2 预检请求 OPTIONS 返回 404 或者 405 的情况这个场景我见过太多。前端用application/json发 POST 请求控制台报的 CORS 错误Network 里看是 OPTIONS 请求 404 了。可能的原因有几个后端没有处理 OPTIONS 路由。尤其是很多手写的路由框架没设置过 OPTIONS 的请求处理。后端虽然写了 CORS 配置但中间件或过滤器顺序没处理好。有一个典型的 Spring Boot 场景你加了CorsFilter但设置了CrossOrigin的 Controller 处理顺序在过滤器之后理论上是兼容的但某些拦截器可能把 OPTIONS 请求给拦下来返回 401/403。Nginx 层直接拦截了 OPTIONS 请求return 204的规则没匹配上。Spring Security 没放行 OPTIONS 请求。这种问题排查的快捷方法是在浏览器的 Network 面板里看预检请求和实际请求的响应状态码。预检请求成功后浏览器才会发送真实的POST请求。所以如果 Network 里只有一个 OPTIONS 请求且报错说明预检没过如果两个请求都在但真实请求失败就要去看真实请求的响应。4.3 自定义响应头读取不到Access-Control-Expose-HeadersCORS 还有一个容易被人忽略的细节默认情况下前端用 JavaScript 只能读取一小部分响应头比如Cache-Control、Content-Type等。如果你想读取后端的自定义响应头比如文件下载的Content-Disposition、分页信息X-Total-Count服务端必须显式地暴露这些头部。对应的响应头是Access-Control-Expose-Headers。后端需要配置registry.addMapping(/**) .exposedHeaders(Content-Disposition, X-Total-Count);如果你忘了这个前端fetch响应里用res.headers.get(Content-Disposition)拿到的一定是null。这个问题和上面提到的 CORS 报错还不一样它不会弹任何错误只会让你的数据莫名其妙“消失”排查起来更隐蔽。4.4 通配符、localhost 与反向代理产生的 Origin 不一致问题最后一种特别经典的坑后端配置了Access-Control-Allow-Origin: *前端页面也是普通请求按说是最宽松的策略了但依然报错。原因可能是请求经过了代理、CDN 或网关目标服务器看到的Origin发生了变化服务端为了安全在代码里动态判断了Origin白名单但因为取下标的Origin带了端口号而后端白名单里忘了写端口后端返回多了一个Access-Control-Allow-Origin响应头且两个值不一样。浏览器不允许出现两个不一致的同名跨域头这种情况下会直接判定失败。排查技巧也很简单在浏览器控制台里用curl模拟请求看返回的响应头和浏览器实际收到的响应头是否一致。如果curl返回头很正常浏览器却报错基本就是代理层或者浏览器缓存导致的。5. 一套我常用的排查“一本道”流程到这里我把自己平时排查跨域的完整流程整理出来你可以直接当 check list 用。第一步确认是不是浏览器环境导致。用 curl、Postman 等工具直接请求接口。如果工具能正常拿到数据说明后端是好的问题在浏览器跨域层面。如果工具也请求失败先解决后端问题别急着调 CORS。第二步看清报错的具体文案。两种常见的报错要区分报错里明确提到 preflight说明预检请求没过。报错里只说没有Access-Control-Allow-Originheader说明实际请求的响应头没有正确返回。第三步在 Network 面板里看请求详情。重点看Origin请求头的值预检请求OPTIONS的响应状态码和响应头实际请求的响应头里是否有Access-Control-Allow-Origin。Access-Control-Allow-Credentials是否为 true。第四步检查后端配置是否与前端请求匹配。建议做成一张自查清单场景必选配置带 Cookie 跨域Access-Control-Allow-Origin指定源 Access-Control-Allow-Credentials: true自定义 Header 跨域Access-Control-Allow-Headers包含对应的 Header自定义方法跨域Access-Control-Allow-Methods包含对应方法前端读自定义响应头Access-Control-Expose-Headers包含对应响应头预检缓存Access-Control-Max-Age设置合理时长第五步检查网关/代理层。如果是 Nginx 反代确认 OPTIONS 请求有没有被正确短路、add_header 有没有被覆盖如果是 Spring Cloud Gateway确认跨域配置和全局过滤器是否有冲突如果是 Cloudflare 等 CDN确认是否会吞掉或者修改某些头。第六步验证生产环境和开发环境差异。有时候本地能通线上跨域。多半是线上域名和你配置的白名单有出入或者线上 HTTPS 和 HTTP 混合导致 Origin 不一致。这种时候我通常会在后端加一行临时的日志把实际收到的 Origin 打出来对照之后立刻就能定位。6. 一些还想多说两句的经验CORS 这个问题本身不复杂但它横跨了协议、前端、后端、网关四个层面任何一个环节掉了链子呈现给用户和开发者的就是一句冷冰冰的英文报错。说实话我见过很多项目组在这个问题上反复扯皮前端说后端没配后端说我们接口用 Postman 测过没问题。老话重提当 Postman 正常而浏览器异常第一步先反思浏览器第二步再怀疑后端 CORS 配置。这个顺序一旦反了排查效率极低。另外还有一点经验能不跨域就尽量不跨域。这听起来像废话但很多项目其实是被自己的架构坑了。前后端分属不同域名本来没问题但你要想清楚如果就一两个静态页面完全可以用 Nginx 把前端静态资源和后端代理到同一个域下面绕开 CORS。配置 CORS 时生产环境一定不要图省事直接*。把允许的来源写明确这样将来接口被第三方调用时你也知道请求方是谁。最后分享一个我自己常用的小习惯在后端接口调试阶段我会把跨域配置里allowedHeaders和allowedMethods都设成*尽快打通联调等联调结束、准备上线前再收敛成明确白名单。这样既能保证开发效率又不至于把安全隐患带到生产环境。再加上maxAge设置大一点比如 3600预检请求的数量会肉眼可见地降下去接口的“响应速度”体验也会好不少。希望这篇能帮你把 CORS 这个老朋友彻底搞定。下次再见到那段报错心里就有谱了。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

男女性别检测数据集VOC+YOLO格式9769张2类别实战指南 2026/10/1 13:25:49

男女性别检测数据集VOC+YOLO格式9769张2类别实战指南

简介:本数据集面向计算机视觉开发者与性别识别模型训练者,提供男女性别二分类检测所需的标注数据,适用于目标检测算法训练、模型微调与教学实验等场景。资源采用Pascal VOC与YOLO双格式组织,包含9769张jpg图片,并配套等…

阅读更多 →
HACLabs靶机渗透实战:从信息收集到权限提升全链路解析 2026/10/1 13:25:49

HACLabs靶机渗透实战:从信息收集到权限提升全链路解析

1. 这不是游戏,是渗透测试的“解剖课”:haclabs靶机到底在练什么?你点开VulnHub上那个标着“haclabs”的靶机镜像,下载、导入VirtualBox、启动——屏幕上跳出一个简陋的登录界面,或者一段静态HTML,甚至可能…

阅读更多 →
Madeira 跨平台兼容层实战:Wine + FEX-Emu + DXMT 与 iOS 工具链整合 2026/10/1 13:25:42

Madeira 跨平台兼容层实战:Wine + FEX-Emu + DXMT 与 iOS 工具链整合

1. 从“Madeira”说起:一个跨平台兼容层的真实项目复盘 第一次看到“Madeira”这个名字,很多人会以为是某个旅游项目或者葡萄酒品牌,毕竟热搜词里挂着 Wine。但如果你是一个长期折腾跨平台兼容层、模拟器、iOS 开发环境的人,就会立…

阅读更多 →
AI工程从零到落地:知识库问答系统全流程实战指南 2026/10/1 13:25:42

AI工程从零到落地:知识库问答系统全流程实战指南

看到“ai-engineering-from-scratch”这个标题,我第一反应不是去看它是不是又一个仓库名或者课程名,而是觉得这个词组值得认真拆开说。AI工程这个词被讨论了很多年,但真正能讲清楚“从零怎么入手”的内容并不多。市面上大多数教程要么让你直接…

阅读更多 →
Allegro学习笔记:封装库路径配置与网络表导入全流程 2026/10/1 13:25:42

Allegro学习笔记:封装库路径配置与网络表导入全流程

Allegro学习笔记这个系列,是我自己硬啃Cadence工具链的记录,第一篇讲了环境安装,这篇是系列第二篇,专门聊两件事:封装库路径指定和网络表导入。其实这两件事在Allegro的使用中属于“基础设施”。很多从OrCAD Capture转…

阅读更多 →
Madeira 跨平台兼容层:Wine、FEX-Emu 与 DXMT 三层翻译链路解析 2026/10/1 13:25:42

Madeira 跨平台兼容层:Wine、FEX-Emu 与 DXMT 三层翻译链路解析

1. 从"Madeira"这个名字说起:一个跨平台兼容层的真实需求 第一次看到"Madeira"这个项目名,很多人会以为是某个度假岛屿或者葡萄酒品牌——毕竟马德拉岛确实以加强型葡萄酒出名。但结合关键词里的 Wine、FEX-Emu、DXMT、x86-64 来看&…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉