CORS

本指南介绍 AdonisJS 应用中的跨域资源共享(CORS)。你将学习如何:

  • 安装并配置 CORS 中间件
  • 控制允许哪些源、方法和请求头
  • 处理跨域请求中的凭据
  • 调试常见的 CORS 错误

概述

当浏览器向与当前页面所在域不同的域发起请求时,会强制执行跨域资源共享(CORS)限制。这种安全机制可防止恶意脚本代表用户向你的 API 发起未授权的请求。

例如,如果你的前端运行在 app.example.com,而 API 运行在 api.example.com,浏览器会阻止来自前端的请求,除非你的 API 显式允许该源。在本地开发期间,当前端运行在 localhost:3000 而 API 运行在 localhost:3333 时,同样适用。

在进行某些跨域请求之前,浏览器会使用 OPTIONS HTTP 方法发送一个预检请求(preflight request)。该预检请求会询问你的服务器允许哪些源、方法和请求头。你的服务器必须返回适当的 CORS 响应头,浏览器才会继续发起实际的请求。

AdonisJS 通过 @adonisjs/cors 包来处理 CORS,该包提供的中间件会自动响应预检请求,并为所有响应附加正确的响应头。

安装

使用以下命令安装并配置该包:

node ace add @adonisjs/cors
查看 add 命令执行的步骤
  1. 使用检测到的包管理器安装 @adonisjs/cors 包。

  2. adonisrc.ts 文件中注册以下服务提供者。

    adonisrc.ts
    {
      providers: [
        // ...其他提供者
        () => import('@adonisjs/cors/cors_provider')
      ]
    }
  3. 创建 config/cors.ts 文件。该文件包含 CORS 的配置设置。

  4. start/kernel.ts 文件中注册以下中间件。

    start/kernel.ts
    server.use([
      () => import('@adonisjs/cors/cors_middleware')
    ])

配置

CORS 配置位于 config/cors.ts 中。以下是包含所有可用选项的默认配置:

config/cors.ts
import app from '@adonisjs/core/services/app'
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: app.inDev ? true : [],
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
  headers: true,
  exposeHeaders: [],
  credentials: true,
  maxAge: 90,
})

export default corsConfig

在开发环境中,origin 被设置为 true 以允许所有源,方便本地前后端搭建。在生产环境中,它默认为空数组 [],即阻止所有跨域请求,直到你显式配置允许的源。

启用和禁用 CORS

enabled 选项可以在不将中间件从中间件栈中移除的情况下将其开启或关闭。当你想在调试期间或特定环境中临时禁用 CORS 时,这会很有用。

config/cors.ts
{
  enabled: process.env.NODE_ENV !== 'test'
}

配置允许的源

origin 选项控制哪些域可以向你的 API 发起跨域请求。这会设置 Access-Control-Allow-Origin 响应头。

要动态允许所有源(响应头会镜像请求发起的源):

config/cors.ts
{
  origin: true
}

要禁止所有跨域请求:

config/cors.ts
{
  origin: false
}

要允许特定的域,提供一个源数组:

config/cors.ts
{
  origin: ['https://app.example.com', 'https://admin.example.com']
}

要使用通配符允许任意源:

config/cors.ts
{
  origin: '*'
}
Warning

credentials 设置为 true 时,通配符 * 不能作为 Access-Control-Allow-Origin 响应头的值。出于安全原因,浏览器会拒绝这种组合。当同时配置了 origin: '*'credentials: true 时,AdonisJS 会自动通过反射请求发起的源,而不是发送字面的 * 来处理这个问题。

对于动态的源校验,可以提供一个回调函数。当允许的源存储在数据库中,或你需要自定义校验逻辑时,这会很有用:

config/cors.ts
{
  origin: (requestOrigin, ctx) => {
    /**
     * requestOrigin 是 Origin 头的值。
     * 返回 true 表示允许,false 表示拒绝。
     */
    const allowedOrigins = ['https://app.example.com']
    return allowedOrigins.includes(requestOrigin)
  }
}

配置允许的方法

methods 选项指定跨域请求允许使用哪些 HTTP 方法。浏览器的预检请求包含一个 Access-Control-Request-Method 头,服务器会将此值与允许的方法进行比对。

config/cors.ts
{
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']
}

配置允许的请求头

headers 选项控制跨域请求中允许哪些请求头。浏览器的预检请求包含一个 Access-Control-Request-Headers 头,列出了客户端想要发送的请求头。

要允许所有请求头:

config/cors.ts
{
  headers: true
}

要允许特定的请求头:

config/cors.ts
{
  headers: ['Content-Type', 'Accept', 'Authorization']
}

对于动态的请求头校验,提供一个回调:

config/cors.ts
{
  headers: (requestHeaders, ctx) => {
    return true
  }
}

暴露响应头

默认情况下,浏览器只会向 JavaScript 暴露有限的响应头集合。exposeHeaders 选项允许你指定客户端应能访问的其他响应头。

config/cors.ts
{
  exposeHeaders: ['X-Request-Id', 'X-RateLimit-Remaining']
}

允许凭据

credentials 选项控制是否可以在跨域请求中包含 cookie、Authorization 头以及 TLS 客户端证书。启用后,服务器会发送 Access-Control-Allow-Credentials: true 响应头。

config/cors.ts
{
  credentials: true
}
Tip

当你的前端需要向你的 API 发送身份验证 cookie 或 Authorization 头时,启用 credentials。如果不启用,浏览器会从跨域请求中剥离凭据。

缓存预检响应

maxAge 选项指定浏览器应缓存预检响应的时长(以秒为单位)。这会减少重复跨域调用的预检请求数量。

config/cors.ts
{
  maxAge: 90
}

maxAge 设置为 null 会完全省略 Access-Control-Max-Age 头。将其设置为 -1 会发送该头但禁用缓存。

常见场景

为单页应用提供 API 服务

当你的 API 和前端部署在不同的域上时,配置 CORS 以允许你前端的源,并携带凭据:

config/cors.ts
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: ['https://app.example.com'],
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
  headers: true,
  credentials: true,
  maxAge: 90,
})

export default corsConfig

不同端口的本地开发

在开发期间,你的前端和后端经常运行在不同的端口上。配置 CORS 以允许你的本地前端源:

config/cors.ts
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: (requestOrigin) => {
    /**
     * 在开发期间允许任意端口上的 localhost。
     */
    return requestOrigin.startsWith('http://localhost')
  },
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
  headers: true,
  credentials: true,
  maxAge: 90,
})

export default corsConfig

无需凭据的公共 API

如果你的 API 是公共的,且不需要 cookie 或身份验证头,你可以使用一个宽松的配置:

config/cors.ts
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: '*',
  methods: ['GET', 'HEAD', 'POST'],
  headers: true,
  credentials: false,
  maxAge: 86400,
})

export default corsConfig

另请参阅