08 Express 5 中配置 cors
1. 安装与类型
npm install cors
npm install --save-dev @types/cors
cors 是运行时依赖。它目前通常通过 @types/cors 提供 TypeScript 类型;安装前也可检查包版本是否已内置类型。
import express from "express";
import cors, { type CorsOptions } from "cors";
const app = express();
2. 最宽松的配置
app.use(cors());
这通常返回 Access-Control-Allow-Origin: *,适合公开、无浏览器凭据的 API 示例,不建议不加思考地用于管理后台。
3. 允许一个固定来源
const corsOptions: CorsOptions = {
origin: "http://localhost:5173",
};
app.use(cors(corsOptions));
Origin 必须精确匹配协议、主机和端口,末尾不要添加路径。
4. 多环境白名单
const allowedOrigins = new Set([
"http://localhost:5173",
"https://test.example.com",
"https://www.example.com",
]);
const corsOptions: CorsOptions = {
origin(origin, callback) {
if (origin === undefined) {
// curl、健康检查、服务间请求等可能没有 Origin。
callback(null, true);
return;
}
if (allowedOrigins.has(origin)) {
callback(null, true);
return;
}
callback(new Error(`不允许的 Origin: ${origin}`));
},
};
不要使用 origin.includes("example.com"):https://example.com.attacker.test 也可能通过。需要支持子域时,应使用 URL 解析并严格校验协议和 hostname。
白名单适合从环境变量读取,但要在服务启动时解析并验证,避免拼写错误上线后才暴露。
5. Cookie 跨域配置
const corsOptions: CorsOptions = {
origin: "https://www.example.com",
credentials: true,
};
前端还要主动开启凭据模式。CORS 只是其中一步;Cookie 通常还需要:
Secure
SameSite=None(确实跨站时)
合理的 Domain 和 Path
CSRF 防护
如果使用 Bearer Token,不要仅因为“有登录”就盲目开启 credentials。
6. 完整配置示例
const corsOptions: CorsOptions = {
origin: allowedOriginsArray,
methods: ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization", "X-Request-Id"],
exposedHeaders: ["Content-Disposition", "X-Request-Id", "X-Total-Count"],
credentials: false,
maxAge: 600,
optionsSuccessStatus: 204,
};
app.use(cors(corsOptions));
不要为了“保险”列出应用从不支持的方法和 Header。配置应与实际客户端协议一致。
7. 全局、Router 和单路由
全局:
app.use(cors(corsOptions));
app.use("/api", apiRouter);
只对一个 Router:
app.use("/public-api", cors(publicCorsOptions), publicRouter);
单一路由:
app.get("/download/:id", cors(downloadCorsOptions), downloadHandler);
如果绝大多数接口策略相同,优先全局配置,避免某个错误路由漏掉 Header。特殊公开接口再局部覆盖。
8. OPTIONS 的处理
当 app.use(cors(...)) 位于路由之前时,应用级中间件通常已经能处理预检。局部路由场景可显式添加:
app.options("/api/{*splat}", cors(corsOptions));
Express 5 使用较新的路径匹配语法;命名通配符比旧教程里的裸 * 更稳妥。是否需要显式 app.options 应结合当前 cors 版本和挂载位置验证。
9. 把拒绝 Origin 交给错误处理器
app.use(cors(corsOptions));
app.use(express.json());
app.use("/api", apiRouter);
app.use((error: unknown, req, res, next) => {
if (error instanceof Error && error.message.startsWith("不允许的 Origin")) {
res.status(403).json({ code: "CORS_ORIGIN_DENIED", message: error.message });
return;
}
next(error);
});
注意:被拒绝的跨源浏览器页面通常仍无法读取这个 JSON,这是预期行为;JSON 主要供服务端日志和非浏览器调试使用。
10. 验证配置
正式请求:
curl -i http://127.0.0.1:8080/api/users \
-H "Origin: http://localhost:5173"
预检:
curl -i -X OPTIONS http://127.0.0.1:8080/api/users \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: authorization,content-type"
至少分别测试允许 Origin、拒绝 Origin、无 Origin、OPTIONS、认证失败和业务错误响应。