在 Web 开发中,你是否遇到过这样的错误?
“Access to XMLHttpRequest at 'http://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy.”
这就是典型的跨域问题。本文将带你从根源理解:
- 什么是跨域?
- 为什么浏览器要限制跨域?
- CORS 是如何解决这个问题的?
- 前后端如何正确配置 CORS?
一、什么是“同源”?什么是“跨域”?
✅ 同源策略(Same-Origin Policy)
这是浏览器的一项安全机制:
只有协议(protocol)、域名(host)、端口(port)完全相同的两个页面,才被认为是“同源”的,可以互相访问资源。
| URL A | URL B | 是否同源 | 原因 |
http://example.com/a |
http://example.com/b |
✅ 是 | 协议+域名+端口一致 |
http://example.com |
https://example.com |
❌ 否 | 协议不同(http vs https) |
http://example.com |
http://api.example.com |
❌ 否 | 域名不同(子域也算不同) |
http://example.com:80 |
http://example.com:8080 |
❌ 否 | 端口不同 |
⚠️ 注意:跨域是浏览器限制,不是服务器限制!
你的请求其实已经发到了服务器,服务器也返回了响应,但浏览器拦截了响应,不给前端 JS 使用。
二、为什么要有跨域限制?
想象一下:
- 你登录了银行网站
bank.com,Cookie 中存有身份凭证; - 此时你又打开了一个恶意网站
evil.com; - 如果
evil.com能随意向bank.com发起 AJAX 请求,就能以你的身份转账!
同源策略正是为了防止这类“CSRF”攻击而设计的。
三、CORS:跨域资源共享(Cross-Origin Resource Sharing)
CORS 是 W3C 制定的标准,允许服务器声明“我信任哪些来源的跨域请求”。
🔄 工作流程(由浏览器自动完成)
- 前端发起跨域 AJAX 请求(如
fetch('http://api.example.com/data')); - 浏览器检测到跨域,自动在请求头中添加
Origin: http://localhost:3000; - 服务器收到请求,检查
Origin是否在白名单中; - 如果允许,服务器在响应头中加入:
Access-Control-Allow-Origin: http://localhost:3000
- 浏览器收到响应,验证
Access-Control-Allow-Origin是否匹配当前源; - 匹配 → 放行数据给 JS;不匹配 → 抛出 CORS 错误。
✅ 整个过程对开发者透明,代码写法与同源请求完全一致。
四、CORS 的两种请求类型
1. 简单请求(Simple Request)
满足以下条件即为简单请求:
- 方法为
GET、POST、HEAD; - 请求头只包含:
AcceptAccept-LanguageContent-LanguageContent-Type(仅限application/x-www-form-urlencoded、multipart/form-data、text/plain)
✅ 特点:只发一次请求,浏览器自动加 Origin 头。
2. 预检请求(Preflight Request)
如果不满足简单请求条件(如使用 PUT、DELETE,或 Content-Type: application/json),浏览器会先发一个 OPTIONS 请求 进行“预检”。
示例流程:
// 1. 浏览器先发 OPTIONS 预检 OPTIONS /api/user HTTP/1.1 Origin: http://localhost:3000 Access-Control-Request-Method: PUT Access-Control-Request-Headers: Content-Type // 2. 服务器响应预检 HTTP/1.1 200 OK Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Methods: GET, POST, PUT, DELETE Access-Control-Allow-Headers: Content-Type Access-Control-Max-Age: 86400 // 预检结果缓存 24 小时 // 3. 预检通过,浏览器再发真实 PUT 请求 PUT /api/user ...
💡 预检是为了确保服务器“知道并允许”这种复杂请求,避免意外暴露接口。
五、后端如何正确配置 CORS?
Spring Boot 示例(最常用)
@Configuration public class CorsConfig { @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOriginPatterns(Arrays.asList("http://localhost:*", "https://yourdomain.com")); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); // 允许携带 Cookie config.setMaxAge(3600L); // 预检缓存 1 小时 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }
关键配置说明:
| 配置项 | 作用 |
allowedOriginPatterns |
允许的源(支持通配符,但 * 不能与 credentials=true 共用) |
allowedHeaders |
允许的请求头 |
allowedMethods |
允许的 HTTP 方法 |
allowCredentials |
是否允许携带 Cookie(设为 true 时,allowedOrigin 不能为 *) |
maxAge |
预检结果缓存时间,减少 OPTIONS 请求 |
六、常见误区
| 误区 | 正确理解 |
| “后端没开 CORS,所以请求发不出去” | ❌ 请求已发出,只是浏览器拦截了响应 |
| “用 Nginx 代理就不用管 CORS” | ✅ 正确!反向代理可将前后端变为同源 |
| “CORS 是为了防止黑客攻击” | ⚠️ 不完全对,主要是防止用户浏览器被滥用 |
| “JSONP 可以替代 CORS” | ⚠️ JSONP 仅支持 GET,且有安全风险,已淘汰 |
七、总结
- 跨域是浏览器的安全策略,不是服务器问题;
- CORS 是标准解决方案,需服务器配合;
- 简单请求直接发,复杂请求先预检(OPTIONS);
- 生产环境应明确指定允许的源,避免
*滥用; - 开发阶段可用代理(如 Vue/React devServer proxy)绕过跨域。
🔑 记住:
“前端负责发起请求,后端负责授权跨域。”
只要前后端协作配置好 CORS,跨域问题迎刃而解!
掌握 CORS,你就不再害怕“跨域错误”,也能更安全地构建现代 Web 应用。