跳到主要内容

Next.js中使用跨源资源共享(CORS)来处理跨域请求

在过去的几年中,Next.js已经成为前端开发的首选框架,同时也用于强大的API开发。在构建API时,花一些时间在跨源资源共享(CORS)上至关重要。

什么是CORS?

CORS是一种安全机制,允许服务器指定哪些来源可以访问和加载Web浏览器中的资源。在这里,“来源”指的是请求来源的协议、域名和端口号的组合。

详细来说,CORS是Web浏览器实现的一种保护系统,用于对跨域请求实施限制。其主要目标是保护用户安全,防止未经授权访问资源和数据。

为了实施CORS,浏览器和服务器交换一组特定的HTTP头信息。

在进行某些类型的跨域请求之前,浏览器会使用HTTP OPTIONS方法向服务器发送预检请求。然后服务器响应一些CORS头信息,指示是否允许跨域。

如果预检请求成功,浏览器将执行实际的跨域请求。否则,由于CORS策略错误而被阻止。

主要的CORS头信息包括:

  • Access-Control-Allow-Origin — 指定可以访问资源的来源
  • Access-Control-Allow-Methods — 添加到预检响应中,以指示允许的HTTP方法,如GETPOSTPUT等。
  • Access-Control-Allow-Headers — 在响应预检请求时返回,以指定当前请求中允许的HTTP头信息
  • Access-Control-Allow-Credentials — 指示浏览器是否应在跨域请求中包含凭据,例如cookies或HTTP认证

CORS允许前端应用程序访问不同域的资源,确保安全的跨域通信。默认情况下,Next.js依赖于同源策略,实施严格的政策。如果你想改变这一点,必须手动配置。

为什么Next.js需要CORS

从v9.4版本开始,Next.js支持API开发。如前所述,Next.js中的API默认不涉及CORS头信息,而是遵循同源策略。这意味着它们只能由同一域内的页面在浏览器中访问。

然而,在某些场景中,你可能希望从其他来源访问这些端点。例如,如果你有一个托管在不同域的应用程序,需要在前端访问这些API,它将被这些跨域限制阻止。

为了避免这个问题,你需要通过在Next.js服务器上配置适当的头信息来启用CORS。这样,你可以明确允许特定来源的跨域请求。托管在受信任域上的应用程序将能够向你的Next.js API路由发出请求。

现在让我们学习如何在Next.js中配置CORS!

学习配置Next.js CORS的先决条件

在进入下一节之前,请确保满足以下先决条件:

  • 你的机器上安装了Node.js v18+
  • 一个Next.js v13+应用程序

如果你的Next.js应用程序版本早于推荐版本,你可以通过遵循官方迁移指南进行升级。

按照本教程,无论你的项目是基于页面路由还是新的应用程序路由,都无关紧要。提出的解决方案在两种场景中都将起作用。

从现在开始,我们将假设你Next.js项目中的所有API路由都在/api路径下。

为所有API路由启用CORS

CORS最常见的方法是在所有API端点上全局设置。在Next.js中实现这一点有很多方法。在这里,你将探索在Next.js中使用CORS的三种方法。

使用headers配置

由于next.config.js中的headers键,你可以手动在Next.js中设置CORS头信息:

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
// 匹配所有API路由
source: "/api/:path*",
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: "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" },
]
}
]
}
}

module.exports = nextConfig

headers()是一个async函数,必须返回一个对象数组。当传入请求匹配source路径正则表达式时,服务器将在响应中包含自定义的HTTP headers

请注意,source必须遵循特定的语法。如果使用了错误的路径正则表达式,Next.js将抛出一个Invalid header found错误,并以source parse failed消息失败。

使用中间件

Next.js中间件使你可以执行特定操作,然后才能完成请求。这也包括在响应中设置CORS HTTP头信息。根据你的项目结构,在pagesappsrc文件夹中初始化一个middleware.js文件,如下所示:

// src/middleware.js
// 或
// src/app/middleware.js
// 或
// src/pages/middleware.js

import { NextResponse } from "next/server";

export function middleware() {
// 检索当前响应
const res = NextResponse.next()

// 向响应添加CORS头信息
res.headers.append('Access-Control-Allow-Credentials', "true")
res.headers.append('Access-Control-Allow-Origin', '*') // 替换为你的实际来源
res.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
res.headers.append(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
)

return res
}

// 指定应用中间件的路径正则表达式
export const config = {
matcher: '/api/:path*',
}

请确保根据你的需求调整这段代码中的CORS策略。

请注意,matcher配置接受一个正则表达式路径,强制中间件只在特定路径上运行。如果使用了错误的值,Next.js将失败并显示一个Invalid middleware found错误消息。

如果你的应用程序依赖于应用程序路由,你可以移除config导出,并将此文件直接放在src/app/api路径中。在这两种情况下,中间件函数将应用于/api路径下的所有API端点。

使用Vercel配置文件

你的Next.js项目很可能托管在Vercel上,因为Vercel是Next.js开发团队的背后。你可以通过在项目根目录中初始化一个vercel.json文件来配置部署在Vercel上的Next.js应用程序的CORS头信息,如下所示:

{
"headers": [
{
"source": "/api/(.*)",
"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": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" }
]
}
]
}

正如你看到的,headers键与next.config.js中的等效键具有相似的语法。不同的是source字段的格式。

或者,你也可以使用以下Vercel无服务器函数设置CORS:

const enableCors = fn => async (req, res) => {
res.setHeader('Access-Control-Allow-Credentials', true)
res.setHeader('Access-Control-Allow-Origin', '*') // 替换为你的实际来源
res.setHeader('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
res.setHeader(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
)

// 特定逻辑用于预检请求
if (req.method === 'OPTIONS') {
res.status(200).end()
return
}

return await fn(req, res)
}

const handler = (req, res) => {
const currentDate = new Date()
res.end(currentDate.toString())
}

module.exports = enableCors(handler)

现在你知道如何在所有Next.js API端点上使用CORS了!

CORS头信息在行动

现在,假设你的前端应用程序需要对/api/hello-world执行一个GET API调用。这是预检请求的样子:

通过2xx HTTP状态代码,你可以知道预检请求已成功执行。现在浏览器被允许执行实际的跨域请求:

专注于“响应头”部分中的CORS头信息。它们与上面方法中定义的头信息相匹配。

在Next.js中使用CORS的一般提示

在结束之前,有一些关于在Next.js中使用CORS的提示和技巧你应该知道。

从环境变量中读取ALLOW头信息值

CORS策略可能会随时间变化。因此,你不应该硬编码它们。相反,你可以像下面这样设置一些环境变量:

ACCESS_CONTROL_ALLOW_CREDENTIALS="true"
ACCESS_CONTROL_ALLOW_ORIGIN="*"
ACCESS_CONTROL_ALLOW_METHODS="GET,OPTIONS,PATCH,DELETE,POST,PUT"
ACCESS_CONTROL_ALLOW_HEADERS="X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"

然后在你的代码中使用它们:

res.headers.append('Access-Control-Allow-Credentials', process.env.ACCESS_CONTROL_ALLOW_CREDENTIALS)
res.headers.append('Access-Control-Allow-Origin', process.env.ACCESS_CONTROL_ALLOW_ORIGIN) // 替换为你的实际来源
res.headers.append('Access-Control-Allow-Methods', process.env.ACCESS_CONTROL_ALLOW_METHODS)
res.headers.append('Access-Control-Allow-Headers', process.env.ACCESS_CONTROL_ALLOW_HEADERS)

太好了!CORS定义逻辑不再涉及硬编码字符串。

允许多个CORS来源

Access-Control-Allow-Origin头是一个二元选项,它接受单个来源或所有来源。你会使用星号*来设置这个头以接受所有域,但这个通配符不能在请求中包含凭据时使用。

如果你想允许多个但不是所有CORS来源,你需要编写自定义逻辑。请记住,你只能通过中间件解决方案来实现这种方法。Next.js中启用CORS的其他两种选项涉及静态文件,不接受自定义行为。

更新middleware.js文件如下:

import { NextResponse } from "next/server";

// 所有允许的来源列表
const allowedOrigins = [
'http://localhost:3000',
'https://example-1.com',
'https://example-2.com',
// ...
'https://example-99.com',
];

export function middleware(req) {
// 检索当前响应
const res = NextResponse.next()

// 检索HTTP“Origin”头信息
// 从传入的请求中
const origin = req.headers.get("origin")

// 如果来源是允许的,
// 将其添加到'Access-Control-Allow-Origin'头信息
if (allowedOrigins.includes(origin)) {
res.headers.append('Access-Control-Allow-Origin', origin);
}

// 向响应添加其余的CORS头信息
res.headers.append('Access-Control-Allow-Credentials', "true")
res.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
res.headers.append(
'Access-Control-Allow-Headers',
'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version'
)

return res
}

// 指定应用中间件的路径正则表达式
export const config = {
matcher: '/api/:path*',
}

基本上,你所要做的就是检查当前浏览器发送的Origin头信息是否匹配那些允许的之一。如果是,就在适当的CORS头信息中指定Origin变量,以启用来自它的跨域请求。

仅对特定端点启用CORS

假设你想要为/api/special-data端点定义一个特定的CORS策略。为此,你可以向next.config.jsheaders()返回的数组添加一个新条目:

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
// 匹配所有API路由
source: "/api/:path*",
headers: [
// 省略以简洁...
]
},
{
source: "/api/special-data",
headers: [
{ key: "Access-Control-Allow-Credentials", value: "false" },
{ key: "Access-Control-Allow-Origin", value: "https://example.com" },
{ key: "Access-Control-Allow-Methods", value: "GET,DELETE,PATCH,POST,PUT" },
{ key: "Access-Control-Allow-Headers", value: "Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date" },
]
}
]
}
}

module.exports = nextConfig

请注意,第二个条目的source字段匹配所需API的端点。这意味着Next.js将优先选择它而不是更通用的第一个条目。

否则,你可以更新middleware.js并根据请求的路径名调整其逻辑:

import { NextResponse } from "next/server";

export function middleware(req) {
// 检索当前响应
const res = NextResponse.next()

// 如果传入的是所需的API端点
if (req.nextUrl.pathname === '/special-data') {
res.headers.append('Access-Control-Allow-Credentials', "false")
res.headers.append('Access-Control-Allow-Origin', 'https://example.com')
res.headers.append('Access-Control-Allow-Methods', 'GET,DELETE,PATCH,POST,PUT')
res.headers.append('Access-Control-Allow-Headers', 'Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date')
} else {
// 通用CORS策略省略以简洁....
}

return res
}

// 指定应用中间件的路径正则表达式
export const config = {
matcher: '/api/:path*',
}

如果你是应用程序路由的用户,你可以通过在src/app/special-data文件夹中创建一个middleware.js文件来获得相同的结果。

太好了!单一来源限制不再是问题!

结论

在这篇文章中,你了解了CORS是什么,以及为什么在后端应用程序中使用它如此重要。更具体地说,我们查看了最关键的CORS头信息以及如何在Next.js中设置它们。

正如你在这里看到的,有几种处理Next.js中CORS的方法。它们都只涉及一些代码行,所以实施安全策略并不需要太多。

得益于Next.js的能力,配置CORS从未如此简单。今天就让你的网站更安全!