cover

为 Next.js@14 中的 API Route 配置 CORS

本文的主要内容是简单介绍了 CORS 的相关内容及如何为 Next.js@14 中的 API Route 配置 CORS。

2023-11-29

heading

CORS 跨源资源共享

Cross-Origin Resource Sharing 跨源资源共享, 指的是服务器允许托管在其他域或其他来源上的 Web 页面向其发送 HTTP 请求的一种机制。要理解 CORS,首先需要了解 Same-Origin Policy 同源策略。

heading

Same-Origin Policy 同源策略

同源策略是一种 Web 安全策略,用于限制一个“源“的文档或者它加载的脚本与另一个“源“的资源进行交互。

所谓的“同源”指的是两个 URL 具有相同的主机(hostname),并且使用相同的协议和端口号。在 Web 应用中,跨源访问有以下这些限制:

列表操作是否可以跨源
跨源网络访问跨源写操作一般是被允许的
跨源资源嵌入一般是被允许的
跨源读操作一般是不被允许的
跨源脚本 API 访问Windowwindow.blur 方法
window.close 方法
window.focus 方法
window.postMessage 方法
只读属性 window.closed
只读属性 window.frames
只读属性 window.length
读/写属性 window.location
只读属性 window.opener
只读属性 window.parent
只读属性 window.self
只读属性 window.top
只读属性 window.window
Locationlocation.replace 方法
只写属性 HTMLAnchorElement.href
跨源数据存储访问Web Storage/IndexDB以源进行分割,无法跨源访问
heading

CORS 的实现

CORS 需要浏览器和服务器相互配合才能实现。

heading

CORS HTTP Headers

跨源资源共享标准新增了一组 HTTP Header,浏览器和服务器可以通过发送或响应这些 HTTP Header 实现跨域访问资源。

HTTP 头添加方说明取值备注
Access-Control-Allow-Origin服务器指示响应的资源是否可以被给定的来源共享origin 或 *如果 Access-Control-Allow-Credentials 为 true,则不能使用通配符 *
Access-Control-Allow-Credentials服务器指示当请求的凭证标记为 true 时,是否可以公开对该请求响应true 或 false当用在对 preflight 预检测请求的响应中时,它指定了实际的请求是否可以使用 credentials
Access-Control-Allow-Headers服务器用在对预检请求的响应中,指示实际的请求中可以使用哪些 HTTP 标头header-name[, header-name]*
Access-Control-Allow-Methods服务器指定对预检请求的响应中,哪些 HTTP 方法允许访问请求的资源method[, method]*
Access-Control-Expose-Headers服务器通过列出标头的名称,指示哪些标头可以作为响应的一部分公开header-name[, header-name]*不在列表中的 header 无法通过 JavaScript 获取
Access-Control-Max-Age服务器指示预检请求的结果能被缓存多久delta-seconds
Access-Control-Request-Headers浏览器用于发起一个预检请求,告知服务器正式请求会使用哪些 HTTP Headersfield-name[, field-name]*
Access-Control-Request-Method浏览器用于发起一个预检请求,告知服务器正式请求会使用哪一种 HTTP Methodmethod
Origin浏览器指示获取资源的请求是从什么源发起的hostnameOrign 中不包含 pathname;标头字段总是被发送。
heading

Preflight request 预检请求

浏览器可以通过预检请求向服务器确认是否支持 CORS:

  1. 当浏览器发送非“简单请求”时,浏览器会自动发送一个预检请求(使用 OPTIONS 方法,并携带
    Access-Control-Request-Method
    Access-Control-Request-Headers
    头)向服务器确认是否可以发送该请求;
  2. 如果服务器允许该请求,则会响应该预检请求(携带对应的
    Access-Control-*
    头);
  3. 当浏览器收到该响应后,再发送正式的请求。

所谓的“简单请求”指的是:

  • Request Method 为:GET、HEAD、POST;
  • 手动添加的 Request Headers 只有以下几种:Accept、Accept-Language、Content-Language、Content-Type、Range;
  • Content-Type Header 的 Value 为: text/plain 或 multipart/form-data 或 application/x-www-form-urlencoded;
  • Range Header 的 Value 使用简单格式,即:bytes=256-、bytes=127-255;
  • 使用 XMLHttpRequest 发送请求时,没有监听 XMLHttpRequest.upload 上的事件;
  • 请求中没有使用 ReadableStream 对象。
heading

在 Next.js@14 中为 API Route 实现 CORS

如上所述,CORS 是通过浏览器和服务器相互配合实现的。在现代浏览器中一般都实现了 CORS 规范,因此一般情况下只需要在服务器中进行响应的配置即可。

在 Next.js@14 中有以下几种方式可以为 API Route 配置 CORS:

  1. next.config.js
    中设置
    headers
    选项
const CORS_HEADERS = [ { key: "Access-Control-Allow-Credentials", value: "true" }, { key: "Access-Control-Allow-Origin", value: "*" }, { key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" }, { key: "Access-Control-Allow-Headers", value: "*" }, ]; /** @type {import('next').NextConfig} */ const nextConfig = { async headers() { return [ { source: "/api/:path*", // 为访问 /api/** 的请求添加 CORS HTTP Headers headers: CORS_HEADERS }, { source: "/specific", // 为特定路径的请求添加 CORS HTTP Headers, headers: CORS_HEADERS } ] } } module.exports = nextConfig
next.config.js
  1. middleware
    中配置:
import { NextResponse } from "next/server"; export function middleware() { const resp = NextResponse.next() resp.headers.append('Access-Control-Allow-Credentials', "true"); resp.headers.append('Access-Control-Allow-Origin', '*'); resp.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT'); resp.headers.append('Access-Control-Allow-Headers', '*'); return res } export const config = { matcher: '/api/:path*' };
middleware.ts

matcher
的具体规则参考官方文档

  1. 如果只是针对某些特定的 Route 才需要配置 CORS,除了通过
    matcher
    指定外,还可以单独在 API Route 中进行配置,比如:
import { NextResponse } from "next/server"; const appendCORS = (resp: NextResponse) => { resp.headers.append("Access-Control-Allow-Credentials", "true"); resp.headers.append("Access-Control-Allow-Origin", "*"); resp.headers.append("Access-Control-Allow-Methods", "GET,DELETE,PATCH,POST,PUT"); resp.headers.append("Access-Control-Allow-Headers", "*"); return resp; }; const setContentType = (resp: NextResponse, body: string) => { resp.headers.set("Content-Type", "application/json; charset=UTF-8"); resp.headers.set("Content-Length", Buffer.from(body).byteLength + ""); return resp; }; export function GET() { const body = JSON.stringify({ status: 1 }); const resp = new NextResponse(body); return appendCORS(setContentType(resp, body)); }
app/api/example/route.ts