先记住这个答案
在 Route Handlers 中,可通过返回的Response对象显式添加 CORS 头部,如Access-Control-Allow-Origin。安全实践中,不要对所有接口统一使用*,而应基于Origin请求头动态校验,仅允许可信来源,并明确Access-Control-Allow-Methods与Access-Control-Allow-Headers。若涉及非简单请求,还需实现OPTIONS处理,返回适当的预检响应头。
- CORS 头部需在响应中显式设置
- 敏感接口勿用通配符 * 允许所有来源
- 预检请求需单独处理 OPTIONS 方法
CORS 在 Route Handlers 中的工作原理
浏览器将跨域 HTTP 请求分为简单请求和预检请求。简单请求(如 GET、HEAD、POST 且 Content-Type 为 text/plain、multipart/form-data 或 application/x-www-form-urlencoded 等)直接发送,但响应需包含 Access-Control-Allow-Origin 等头部,否则浏览器拦截。其余情况会先发送 OPTIONS 预检,服务器必须正确响应 OPTIONS 并返回允许的方法、头部等,否则实际请求不会发出。
在 Route Handlers 中,每个 Handler 返回一个 Response,可自由设置 headers。因此配置 CORS 就是创建响应时添加对应头部。Next.js 不会自动添加 CORS 头,除非使用中间件或反向代理。若不定义 OPTIONS,Next.js 默认会生成一个基于已定义方法的简单 OPTIONS 响应,但通常需要自定义以包含预检所需头部。
允许特定前端的 JSON POST 接口
场景:前端订单查询接口 app/api/order/route.ts,仅允许 https://shop.example.com 调用。实现时,读取 request.headers.get('origin'),如果等于允许来源,则在响应头设置 Access-Control-Allow-Origin 为该来源,并加上 Vary: Origin;否则不设置。针对 POST 预检,定义 OPTIONS 方法,返回 204,并设置 Access-Control-Allow-Methods: POST 及允许的请求头。
若需要携带凭证(如 Authorization 头),不能使用 * 通配符,必须明确回显具体 Origin,并设置 Access-Control-Allow-Credentials: true。此外,需验证 OPTIONS 请求的 Access-Control-Request-Method 是否与允许列表匹配,若不匹配则返回 403。此做法将风险控制在可信来源内。
const ALLOWED_ORIGINS = ['https://shop.example.com'];
export async function OPTIONS(request: Request) {
const origin = request.headers.get('origin');
if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
return new Response(null, { status: 403 });
}
const requestedMethod = request.headers.get('access-control-request-method');
if (requestedMethod !== 'POST') {
return new Response(null, { status: 403 });
}
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': origin,
'Vary': 'Origin',
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
export async function POST(request: Request) {
const origin = request.headers.get('origin');
if (!origin || !ALLOWED_ORIGINS.includes(origin)) {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
// 处理业务逻辑
return Response.json({ ok: true }, {
headers: {
'Access-Control-Allow-Origin': origin,
'Vary': 'Origin',
},
});
}演示了动态校验 Origin 并回显,避免通配符。Vary: Origin 告知缓存依据 Origin 区分。
CORS 配置的失败边界与适用条件
使用 * 通配符时,无法携带 Cookie,若接口依赖 Cookie 鉴权将失效。但如果仅用于公开只读数据,可接受。另一个陷阱是:当校验失败时,简单请求会收到响应但被浏览器拦截,实际请求已经到达服务器;若服务器误将 Origin 反射(如直接取请求头)就会允许任何来源,造成风险。
处理预检时若省略 Access-Control-Allow-Headers,浏览器会拒绝带自定义头的请求。同时,设置 Access-Control-Max-Age 可缓存预检结果减少往返,但更新策略时需注意缓存。若要全局配置,可考虑 Middleware 或 next.config.js 的 headers,但 Route Handler 内配置更适合有特定安全要求的接口。
容易答错的地方
- 错误假设 Next.js 自动提供 CORS
- 多人误以为 Next.js Route Handlers 默认允许跨域,实则必须手动设置响应头。若不设置,浏览器会阻止跨域响应,服务端可能仍处理了请求。安全上必须显式配置。
- 反射任意 Origin 为最大风险
- 直接设置
Access-Control-Allow-Origin: request.headers.get('origin')会让恶意网站也能调用接口,导致 CSRF 或其他攻击。必须校验来源是否在可信列表中。
面试官还会怎么问?
如果允许的来源不固定,如何动态配置?
应根据请求的 Origin 查询数据库或配置列表,判断是否允许,并动态返回。而非简单反射。同样需加入 Vary 头以利于缓存。
如何处理带 Cookie 的跨域请求?
必须设置 Access-Control-Allow-Credentials: true,且 Access-Control-Allow-Origin 不能为 *,必须明确指定具体来源。另外客户端需设置 credentials: 'include'。
OPTIONS 方法必须单独实现吗?
如果只处理简单请求,可不实现。但若使用 Authorization 头或非简单 Content-Type,就必须实现 OPTIONS 并返回正确的 Access-Control-* 响应头。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。