CORS 跨域配置:安全地开放 API 访问
CORS 跨域配置:安全地开放 API 访问
在现代 Web 开发中,你是否遇到过浏览器控制台抛出“has been blocked by CORS policy”的错误?这是浏览器的同源策略在保护你的用户,但也常常让前后端开发者感到头疼。本教程将从零开始,带你理解 CORS 的工作原理,学会如何在常见的后端框架中正确配置跨域访问,并在安全的前提下让 API 能被外部网页调用。
什么是 CORS?为什么需要它?
同源策略是浏览器最核心的安全机制。如果两个 URL 的协议、域名和端口号都相同,它们就是同源的。否则,称为跨源(跨域)。
| 对比项 | URL1 | URL2 | 是否同源 | 原因 |
|---|---|---|---|---|
| 同域名不同路径 | http://api.site.com/a |
http://api.site.com/b |
是 | 协议、域名、端口相同 |
| 不同子域名 | http://app.site.com |
http://api.site.com |
否 | 域名不同 |
| 不同协议 | http://site.com |
https://site.com |
否 | 协议不同 |
| 不同端口 | http://site.com:3000 |
http://site.com:4000 |
否 | 端口不同 |
出于安全考虑,浏览器默认禁止页面内的 JavaScript 发出跨源 HTTP 请求。但现代 Web 应用经常需要跨域通信(例如前端在 localhost:3000,API 在 localhost:8080)。CORS(跨源资源共享) 就是一套由 W3C 制定的机制,允许服务器声明自己信任哪些源,从而让浏览器“放行”跨域请求。
CORS 请求的分类
浏览器将跨域请求分为两类,处理方式差异较大。
简单请求(Simple Request)
同时满足以下所有条件:
- 请求方法为
GET、HEAD或POST - 只使用了浏览器自动设置的请求头(如
Accept、Content-Language)以及少数安全头部 - 如果是
POST请求,Content-Type仅限于application/x-www-form-urlencoded、multipart/form-data或text/plain - 请求中没有使用
ReadableStream
对于简单请求,浏览器会直接发出请求,并检查响应头中是否包含合法的 Access-Control-Allow-Origin。如果没有或值与当前源不匹配,JavaScript 就无法读取响应。
预检请求(Preflight Request)
非简单请求(例如 PUT、DELETE,或自定义请求头,或 Content-Type: application/json),浏览器会先发送一个 OPTIONS 请求,称为“预检”。预检会携带以下请求头询问服务器:
Origin:请求的源Access-Control-Request-Method:实际请求将要使用的 HTTP 方法Access-Control-Request-Headers:实际请求将要携带的额外请求头(逗号分隔)
服务器必须在响应中明确允许这些方法和头部,浏览器才会继续发出真正的请求。预检可以被缓存(通过 Access-Control-Max-Age 控制),避免重复询问。
关键的 CORS 响应头
正确配置 CORS 的核心就是设置以下响应头。不需要全部记住,但理解每个头的作用可以帮助你灵活且安全地开放 API。
Access-Control-Allow-Origin
这是必须的一个头。它的值可以是:
- 具体源:如
https://myapp.com,只允许该源 *:允许任意源(不能与凭证请求一起使用)
注意:响应中不能有多个 Access-Control-Allow-Origin 头部。如果需要支持多个指定的源,服务器需要根据请求的 Origin 动态返回匹配的值。
Access-Control-Allow-Methods
用于预检响应,指定允许的 HTTP 方法,如 GET, POST, PUT, DELETE。
Access-Control-Allow-Headers
用于预检响应,指定允许的请求头,如 Content-Type, Authorization, X-Requested-With。
Access-Control-Allow-Credentials
当请求需要携带 Cookie 或 HTTP 认证信息(credentials: 'include')时,必须设置此头为 true。此时 Access-Control-Allow-Origin 不能设为 *,必须指定明确的源。
Access-Control-Max-Age
预检缓存时间,单位为秒。在此时间内,浏览器对同一 URL 不再发出预检请求。
Access-Control-Expose-Headers
默认情况下,跨域请求的 JS 只能拿到 Cache-Control、Content-Language、Content-Type 等少量响应头。如果前端需要读取自定义头(如 X-Total-Count),必须通过这个头部列出它们。
实战:在不同后端框架中配置 CORS
以下示例均基于安全的配置原则:只允许可信的源,明确方法,控制凭据传递。
1. Node.js (Express) 手动配置中间件
const express = require('express');
const app = express();
// 自定义 CORS 中间件
app.use((req, res, next) => {
const allowedOrigins = ['https://your-frontend.com', 'http://localhost:3000'];
const origin = req.headers.origin;
if (allowedOrigins.includes(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
// 处理预检请求
if (req.method === 'OPTIONS') {
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400'); // 缓存24小时
return res.status(204).end();
}
next();
});
app.get('/data', (req, res) => {
res.json({ message: 'CORS 配置成功' });
});
app.listen(8080);
使用 cors 中间件(推荐) 你也可以直接使用成熟的 npm 包,代码更简洁:
const cors = require('cors');
app.use(cors({
origin: ['https://your-frontend.com', 'http://localhost:3000'],
credentials: true,
methods: 'GET,POST,PUT,DELETE',
allowedHeaders: 'Content-Type,Authorization',
maxAge: 86400
}));
2. Python (Flask)
使用 flask-cors 扩展:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# 全局配置
CORS(app, origins=['https://frontend.com', 'http://localhost:3000'],
methods=['GET', 'POST', 'PUT', 'DELETE'],
allow_headers=['Content-Type', 'Authorization'],
supports_credentials=True,
max_age=86400)
# 也可在单个路由上装饰器配置
@app.route('/api/data')
def data():
return {"message": "Hello from Flask"}
3. Java (Spring Boot)
在控制器或全局配置中添加注解,或使用配置类。 方法一:注解
@CrossOrigin(origins = "https://frontend.com", maxAge = 3600)
@RestController
public class ApiController { ... }
方法二:全局配置(推荐)
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://frontend.com", "http://localhost:3000")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Content-Type", "Authorization")
.allowCredentials(true)
.maxAge(3600);
}
}
4. Nginx 反向代理层配置
如果你的 API 通过 Nginx 代理,可以在 Nginx 处统一处理 CORS,无需后端介入。
server {
listen 80;
location /api/ {
# 处理 OPTIONS 预检
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://frontend.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-Max-Age' 86400;
return 204;
}
# 正常请求
add_header 'Access-Control-Allow-Origin' 'https://frontend.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://backend:8080;
}
}
安全配置最佳实践
CORS 的初衷是安全放开,而不是放开安全。以下几条原则能帮你避免常见的安全隐患。
1. 永远不要使用 * 且随意携带凭据
Access-Control-Allow-Origin: * 不能与 Access-Control-Allow-Credentials: true 同时使用。一旦允许携带凭据,就必须指定确切的源列表,并严格校验请求中的 Origin 是否在其中。
2. 严格校验 Origin
后端应当检查 Origin 是否为信任列表中的值,然后动态设置,而不是直接从请求头中提取并原样返回。恶意请求可能伪造 Origin,但浏览器会如实发送当前页面的源,你的校验逻辑能有效防御。
3. 限制允许的方法和头部
只开放你的 API 真正需要的 HTTP 方法和头部。例如一个只读的公开 API,GET 足矣,无需暴露 PUT、DELETE。
4. 设置合理的 Access-Control-Max-Age
不要将预检缓存的时间设置得过长(如超过 24 小时),以免规则变更时浏览器仍使用旧的缓存策略。通常 1 小时(3600)到 24 小时(86400)之间是合理的。
5. 区分公开 API 与需认证的 API
公开数据接口可以使用固定的 * 或者一个白名单源。涉及用户数据的接口务必启用 Credentials 并严格控制源。
6. 不要依赖 CORS 作为一种身份验证机制
CORS 只是浏览器端的“放行”机制,后端自己的鉴权(如 Token、Session)必须独立且强健。永远不要假设因为设置了 CORS 就可以免去请求签名或 Token 验证。
常见问题排查清单
当浏览器提示 CORS 错误时,可以从以下角度快速定位。
-
错误信息包含“No ‘Access-Control-Allow-Origin’ header”
→ 服务器未返回该头部,或返回的源与当前页面的源不匹配。检查后端 CORS 配置。 -
预检请求失败(OPTIONS 返回 405/403)
→ 服务器未正确处理 OPTIONS 请求。确保后端或网关层响应 OPTIONS 并返回 204。 -
请求已成功但前端获取不到自定义响应头
→ 检查服务器是否设置了Access-Control-Expose-Headers,并将需要的头部列入其中。 -
使用了 credentials 但依然被拦截
→ 确认后端Access-Control-Allow-Origin不是*,且Access-Control-Allow-Credentials为true。 -
本地开发环境正常,生产环境报错
→ 生产环境 Origin 可能与本地不同,检查生产环境的允许源列表是否包含了实际的域名。 -
移动端或 Postman 中请求正常,浏览器中异常
→ 因为 CORS 是浏览器的安全策略,其他工具不实施同源策略,这是正常现象。切勿因为 Postman 成功就认为配置无问题。
总结
CORS 是连接前端与后端的一座“受控桥梁”,正确配置它可以让你的 Web 应用安全地拥抱跨域资源共享。记住它的工作流程:浏览器自动区分简单请求与预检请求,服务器通过特定的响应头告知浏览器哪些跨域行为是允许的。遵循“最小权限”原则,谨慎开放源和方法,你就能在安全与功能之间找到完美平衡。
现在,根据你使用的技术栈,动手调整一下 API 的 CORS 设置吧。如果你遇到任何其他跨域问题,欢迎查阅本文的排查清单,它往往能帮你快速定位症结。