CORS 基础:跨域资源共享配置与原理

文章目录

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

前言:那个让我通宵的报错

兄弟们,我先说个真事儿。

去年三月份,我接了个外包项目,甲方爸爸要求前后端分离,前端Vue3,后端Spring Boot。我拍着胸脯说没问题,三天搞定。结果第三天凌晨四点,我还盯着控制台那一行红字发呆:

Access to XMLHttpRequest at 'http://localhost:8080/api/login' from origin 'http://localhost:5173' has been blocked by CORS policy...

当时我就破防了!这什么鬼东西?!我代码写得没毛病啊,axios配置也对,后端接口也能跑,咋就数据拿不到呢?

后来天亮了,我在楼下买了杯美式(别问为什么是美式,因为生活太苦了),终于搞明白了------这特么就是传说中的跨域问题。今天这篇文章,我不跟你讲大道理,就聊聊这个让无数程序员掉头发的CORS,到底是咋回事,以及我怎么解决它的。

啥是CORS?一句话说不清楚

首先啊,CORS全称是Cross-Origin Resource Sharing,翻译过来叫"跨域资源共享"。听着挺高大上的对吧?其实就是浏览器的一种安保机制。

我给你打个比方。想象你现在住在一个高档小区,小区门口有保安大爷,大爷手里拿着个花名册。按照小区规定,只有业主(同源)能随便进出,外来人员(跨域)要进小区,必须得业主事先跟保安打过招呼,登记备案,才能放行。

浏览器就是那个保安大爷。你的网站前端跑在http://localhost:5173,这是你家。后端API跑在http://localhost:8080,这是隔壁老王家。浏览器一看,协议不同?端口不同?域名不同?得了,默认情况下就是不让进。

等等,你可能会问:"那我直接用Postman调用接口咋就没问题?"

问得好!这就是最坑的地方。CORS只针对浏览器!你用Postman、用curl、写Python脚本,随便调,完全没限制。但一旦涉及到浏览器里的JavaScript发请求,保安大爷立马警觉------"站住!有嫌疑!"

同源策略:浏览器的"被害妄想症"

要说清楚CORS,得先聊聊它背后的逻辑------同源策略(Same-Origin Policy)。

这个策略是1995年Netscape浏览器搞出来的,那时候互联网还挺单纯,但网景公司就已经有"被害妄想症"了。他们担心,如果一个恶意网站能通过JavaScript随便访问其他网站的数据,那还得了?比如你在A网站登录了银行账号,然后不小心点开B网站,B网站的JS就能直接调银行接口转钱?这特么不是完犊子了!

所以同源策略规定:协议、域名、端口,三者必须完全一致,才算"同源"。

看到没?就这么严格。哪怕只是端口从80变成8080,保安大爷立马翻脸不认人。

但问题来了,现代web开发哪有不分前后端的?微服务架构下,API跟前端部署在不同域名上是常态。这时候如果浏览器死守着同源策略不放,那大家都不用干活了。所以,CORS机制应运而生------它不是取消同源策略,而是给了一个"白名单"机制,告诉浏览器:"这几家是我亲戚,放行吧。"

CORS到底怎么工作的?两种模式

好,现在进入硬核(其实也不硬)部分。CORS请求分成两种类型:简单请求和预检请求(preflight)。浏览器会自动判断走哪种流程,你控制不了,只能被动接受。

简单请求:直接放行

啥算简单请求?得同时满足下面一堆条件:

  1. 方法只能是GET、HEAD、POST之一
  2. 头部字段只能有Accept、Accept-Language、Content-Language、Content-Type(但Content-Type只能是application/x-www-form-urlencoded、multipart/form-data或text/plain)
  3. 不能有你自定义的Header

说实话,现在写代码,谁还不带个Authorization头?谁还不发个JSON(application/json)?所以真实项目里,简单请求几乎不存在!我写了这么多年代码,简单请求就遇到过一次,还是静态资源加载。

简单请求的流程是这样的:浏览器直接发出请求,但会在Header里加一个Origin字段,告诉服务器:"我来自http://localhost:5173,给句话,能不能访问?"

服务器如果愿意放行,就在响应头里加:

复制代码
Access-Control-Allow-Origin: http://localhost:5173

或者更暴力的:

复制代码
Access-Control-Allow-Origin: *

浏览器收到响应,一看"哦,允许了",就把数据交给JavaScript。如果服务器没返回这个头,或者返回的Origin不匹配,浏览器就当场拦截,你JavaScript拿到的就是个大红报错。

预检请求:先问再问

这个才是我们日常开发的常态!当你发一个POST请求,Content-Type是application/json,还带了Authorization头,浏览器立马紧张起来:"这请求有点复杂啊,我得先探探路!"

于是,在真正的请求之前,浏览器会自动发一个OPTIONS请求(这叫预检),问服务器:"大哥,我打算用POST方法,带这些头,发JSON数据,你行不行?"

这个OPTIONS请求会带三个关键头:

  • Origin: 还是表明来源
  • Access-Control-Request-Method: POST(我准备用的方法)
  • Access-Control-Request-Headers: content-type,authorization(我准备带的头)

服务器这时候得表态,返回:

复制代码
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

看到没?服务器明确说:我允许这些方法,允许这些头。浏览器一听,妥了,才会发出真正的POST请求。如果服务器没响应,或者响应里没说允许,浏览器就直接掐断,真正的请求压根发不出去!

那个Access-Control-Max-Age是缓存时间,单位秒。意思是这次预检结果我记住了,86400秒内(也就是24小时)不用再问。

实战配置:我怎么解决那个通宵问题

好了,原理讲完了,说说我是怎么解决凌晨四点的绝望的。

Spring Boot后端配置

我那个项目用的是Spring Boot 3.2(2025年的版本,别用2.x了兄弟们,该升级了)。最暴力的方法,在Controller上加注解:

java 复制代码
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://localhost:5173", maxAge = 3600)
public class UserController {
// ... 你的代码
}

但这太麻烦了,每个Controller都加?我懒。所以全局配置:

java 复制代码
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")  // 所有路径
            .allowedOrigins("http://localhost:5173", "http://127.0.0.1:5173")
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedHeaders("*")
            .allowCredentials(true)  // 允许携带cookie
            .maxAge(3600);
}

}

注意!这里有个大坑!allowedOrigins("*")和allowCredentials(true)不能同时用!这是Spring Boot的安全限制。如果你需要带Cookie(比如登录态),必须指定具体域名,不能用星号。

Nginx反向代理方案(生产环境推荐)

后来项目上线,我换了种方案,用Nginx做反向代理。这才是正解!

nginx 复制代码
server {
listen 80;
server_name myapp.com;
location /api {
    # 转发到后端服务
    proxy_pass http://localhost:8080;
    
    # 关键是这几行!
    add_header 'Access-Control-Allow-Origin' 'https://myapp.com' always;
    add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
    add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;
    
    # 处理预检请求
    if ($request_method = 'OPTIONS') {
        return 204;
    }
}

location / {
    proxy_pass http://localhost:5173;  # 前端服务
}

}

看到没?Nginx层就把CORS处理了,后端代码完全不用改!而且这样还能隐藏真实后端地址,安全性++。

Node.js/Express配置

如果你写Node,是这样的:

javascript 复制代码
const express = require('express');
const cors = require('cors');  // 记得npm install cors
const app = express();
// 方案1:允许所有(开发环境凑合用)
app.use(cors());
// 方案2:生产环境严谨点
app.use(cors({
origin: ['http://localhost:5173', 'https://production.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
}));

Express这个cors中间件是2025年1月刚更新的2.8.5版本,修复了几个安全漏洞,记得升级。

我踩过的那些天坑(血泪史)

坑1:缓存导致的诡异现象

有一次我改了后端配置,刷新了浏览器,居然还是报错!我以为是配置没生效,重启了N遍服务,差点把电脑砸了。后来才发现,是Access-Control-Max-Age的锅。浏览器把之前的预检结果缓存了,我改的配置它压根没去问!

解决方案:开发环境把maxAge设成0,或者手动禁用浏览器缓存(Chrome DevTools里有个Disable Cache)。

坑2:Vite热更新的锅

用Vite的兄弟们注意!Vite的热更新(HMR)用WebSocket,有时候CORS配好了API请求,但HMR还是连不上。这时候得在vite.config.js里单独配:

javascript 复制代码
export default defineConfig({
server: {
hmr: {
overlay: false,
},
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
}
}
}
});

用Vite的代理转发,直接绕过浏览器的CORS!这招在开发环境简直神器,前端代码里请求/api/xxx,Vite帮你转发到8080端口,浏览器看来都是5173同源,完美!

坑3:复杂请求体触发的预检

我有一次发了个DELETE请求,按理说也是简单方法对吧?但浏览器还是发了OPTIONS预检。查了半天,发现是因为我在DELETE请求里带了JSON body。某些浏览器(说的就是Safari)对DELETE带body的情况有特殊处理,会触发预检。

解决方案:DELETE请求尽量把参数放URL里,别放body里。或者老老实实让服务器支持OPTIONS响应。

安全警告:别无脑开*

最后说点严肃的。我看到很多教程教人直接Access-Control-Allow-Origin: ,还配上Access-Control-Allow-Credentials: true。这特么是在玩火!
2025年OWASP发布的API安全Top 10里,CORS配置错误依然排在第6位。如果你把
开放到生产环境,还允许携带Cookie,那攻击者随便做个钓鱼网站就能冒充用户请求你的API,这叫CSRF攻击的变种,防不胜防!

正确的做法:

  • 生产环境白名单必须明确指定域名
  • 内部系统考虑用IP白名单+VPN,别直接暴露在公网
  • 敏感接口(转账、删数据)就算开了CORS,也要额外验证Referer和Origin头
  • 定期用curl -H "Origin: https://evil.com"测试你的API,看有没有配置泄露

总结与互动

写到这儿,天又快黑了(我看了一眼时间,下午5点25分)。突然有种回到去年那个凌晨的错觉,只不过现在手里是可乐而不是美式。

CORS这个东西吧,原理其实不复杂,就是浏览器的"门禁系统"。但坑就坑在它涉及前后端、浏览器、服务器、网络层多个环节,报错信息还模棱两可,"blocked by CORS policy"------鬼知道是哪一步blocked的?

今天这篇文章,我从那个让我通宵的报错说起,聊了同源策略的"被害妄想症",拆解了简单请求和预检请求的区别,给了Spring Boot、Nginx、Node.js的现成配置代码,还倒了几盆我踩过的天坑。

但是! 我知道你们肯定还有自己的问题。比如"我的情况更复杂,用了网关+微服务咋配?"或者"WebSocket的CORS怎么处理?"再或者"为啥我配好了Postman能调,浏览器还是不行?"

来,评论区见!把你遇到的CORS奇葩报错贴出来,咱们一起研究。点赞过500的话,我下周出个进阶篇,专门讲复杂架构下的CORS治理方案,还有怎么在微服务网关层(Spring Cloud Gateway 2025新版)统一处理跨域,保证让你再也不怕那个红色报错!

最后,转发这篇文章给你那个还在被CORS折磨的同事,救人一命胜造七级浮屠啊朋友们!

P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。

相关推荐
像颗糖2 小时前
Ollama Linux 服务器本地部署
linux·人工智能·全栈
帐篷Li2 小时前
Claude Mythos成为首个完成AISI网络安全全流程评测的模型
人工智能
neoooo2 小时前
Spring AI MCP Server 开发指南
人工智能·后端·mcp
雾喔2 小时前
【学习笔记2】快速上手调用 AI API & Prompt Engineering
人工智能·笔记·学习
黑不溜秋的2 小时前
AI文章阅读 - 大语言模型介绍
人工智能
算.子2 小时前
【Spring 实战】Spring AI 进阶专题:Token 成本优化与 Structured Output
java·人工智能·spring
linyb极客之路2 小时前
OpenSpec Commands 全解析:让 AI 编码工作流更规范高效
人工智能
小瓦码J码2 小时前
如何手动部署一个向量模型服务
人工智能·后端