深入实战:如何在 Express 中完美配置 CORS(跨域资源共享)

作为一名 Web 开发者,你是否曾在控制台中见过那令人沮丧的红色报错信息:“Access to XMLHttpRequest at ‘http://localhost:5000‘ from origin ‘http://localhost:3000‘ has been blocked by CORS policy”?

这确实是我们在构建全栈应用时最常遇到的障碍之一。在这篇文章中,我们将不仅仅满足于“解决”报错,而是深入探讨什么是 CORS,以及如何在 Express.js 中优雅、安全地配置它。我们将一起探索从全开放到严格白名单的各种场景,让你在未来的开发中不再惧怕跨域问题。

什么是 CORS?

在深入代码之前,让我们先理解一下我们在对抗什么。你肯定听说过“同源策略”吧?这是浏览器的一项核心安全机制,用来限制一个源的文档或脚本如何能与另一个源的资源进行交互。所谓“同源”,是指两个 URL 具有相同的协议(如 http)、主机名(如 example.com)和端口(如 8080)。

默认情况下,为了安全起见,浏览器会阻止前端代码(比如你在 localhost:3000 运行的 React 应用)去访问不同源的后端 API(比如运行在 localhost:5000 的 Express 服务)。这正是 CORS(跨源资源共享) 登场的时候。

CORS 是一种基于 HTTP 头的机制,它允许服务器通过在响应中包含特定的 HTTP 头,来明确告知浏览器:“嘿,虽然我们不是同一个源,但我允许这个源访问我的资源”。作为开发者,我们的任务就是在 Express 服务器中正确配置这些头部信息。

准备工作:项目搭建

为了更好地演示,让我们一起从零开始构建一个演示项目。我们将创建一个包含服务器端和客户端的完整示例。

第一步:初始化项目

首先,让我们打开终端,创建一个新的目录并初始化 Node.js 应用。为了方便演示,我们将其命名为 cors-demo

# 创建项目目录并进入
mkdir cors-demo

# 初始化 package.json
npm init -y

第二步:安装依赖

我们将使用 Express 作为服务器框架,并使用官方的 cors 中间件来简化配置。这个中间件是 Express 社区处理跨域问题的标准解决方案。

# 安装 express 和 cors
npm install express cors

第三步:构建目录结构

为了模拟真实的开发场景,我们需要一个服务端和一个客户端。请在你的项目根目录下创建以下结构:

  • server.js:我们的后端服务入口。
  • client/:一个用于存放前端文件的文件夹。

* client/index.html:前端页面。

* client/script.js:前端逻辑。

场景一:允许所有来源(开发环境)

很多时候,尤其是在本地开发阶段,我们希望快速验证功能,而不想被复杂的权限限制住。在这种情况下,我们通常允许所有来源访问我们的 API。请注意,这种方式不建议在生产环境中使用,但在原型设计阶段非常高效。

客户端代码

首先,让我们编写一个简单的前端代码,尝试去请求后端的秘密数据。

client/index.html





    
    
    CORS 演示页面
    
    
    
        body { font-family: sans-serif; padding: 2rem; }
    



    

检查控制台获取数据

打开浏览器开发者工具查看请求结果。

client/script.js

// 尝试从我们的后端 API 获取数据
// 注意:由于端口不同,这通常是一个跨域请求
fetch(‘http://localhost:5000/secret‘)
    .then(response => response.json())
    .then(data => {
        console.log(‘成功获取数据:‘, data);
        alert(‘秘密数字是: ‘ + data.secret);
    })
    .catch(error => {
        console.error(‘请求失败:‘, error);
    });

服务器端代码

现在,让我们来编写服务器端代码。如果不配置 CORS,上面的前端代码会报错。让我们看看如何用最简单的方式解决这个问题。

server.js

// 引入必要的模块
const express = require(‘express‘);
const cors = require(‘cors‘);

// 创建 Express 应用实例
const app = express();

// ============================================
// 核心配置:启用 CORS
// ============================================
// 使用默认配置,允许所有源访问
// 这会添加 Access-Control-Allow-Origin: * 响应头
app.use(cors()); 

// ============================================
// API 路由定义
// ============================================

// 根路由 - 欢迎信息
app.get(‘/‘, (req, res) => {
    res.json({ message: "欢迎来到我们的服务器!" });
});

// 秘密路由 - 模拟敏感数据接口
app.get(‘/secret‘, (req, res) => {
    // 生成一个随机秘密数字
    const secretNumber = Math.floor(Math.random() * 100);
    res.json({ secret: secretNumber });
});

// ============================================
// 启动服务器
// ============================================
const PORT = 5000;

app.listen(PORT, () => {
    console.log(`服务器正在端口 ${PORT} 上运行...`);
});

运行测试:

  • 启动服务器:node server.js
  • 在浏览器中打开 INLINECODE4d74f1f6(你需要使用 Live Server 或类似的简单 HTTP 服务器来运行 HTML 文件,直接双击打开文件可能因为 INLINECODE8dd6f42a 协议导致其他问题,但为了演示 CORS,确保使用 http 协议访问前端)。

如果你看到控制台输出了秘密数字,恭喜你!CORS 已经成功配置。

场景二:特定源白名单(生产环境推荐)

在实际的生产环境中,允许所有源(INLINECODE23c828be)通常不是一个好主意,因为这可能会导致安全风险或 CSRF 攻击。通常,我们会明确知道前端应用的域名,例如 INLINECODE1109980f 或 http://localhost:3000

我们可以通过配置 origin 选项来实现这一点。

高级配置代码示例

让我们修改 server.js,只允许特定的源访问我们的资源。

server.js (更新版)

const express = require(‘express‘);
const cors = require(‘cors‘);

const app = express();

// ============================================
// CORS 配置选项
// ============================================
const corsOptions = {
    // 这是一个函数,用来动态检查请求的 origin 是否在白名单中
    origin: function (origin, callback) {
        // 允许的源列表(白名单)
        const whitelist = [
            ‘http://localhost:5500‘,  // 你的本地开发前端地址
            ‘http://127.0.0.1:5500‘,
            ‘https://my-production-app.com‘ // 你的线上前端地址
        ];
        
        // 注意:在某些请求中(如移动端 App 或 Postman),origin 可能为 undefined
        // 这里我们根据实际需求决定是否允许无 origin 的请求
        if (whitelist.indexOf(origin) !== -1 || !origin) {
            callback(null, true); // 允许访问
        } else {
            callback(new Error(‘Not allowed by CORS‘)); // 拒绝访问
        }
    },
    
    // 是否允许发送 Cookie (凭证)
    // 如果设为 true,origin 不能为 ‘*‘
    credentials: true, 
    
    // 预检请求的有效期(秒)
    optionsSuccessStatus: 200 
};

// 将配置传入 cors 中间件
app.use(cors(corsOptions));

// 测试路由
app.get(‘/‘, (req, res) => {
    res.json({ message: "欢迎访问安全的服务器!" });
});

app.get(‘/secret‘, (req, res) => {
    const data = { secret: "只有白名单内的网站能看到这个", user: "Admin" };
    res.json(data);
});

// 错误处理中间件(用于处理 CORS 拒绝导致的错误)
app.use(function (err, req, res, next) {
    if (err) {
        // 这里的错误通常是 CORS 阻止了请求,所以浏览器可能收不到这个响应
        // 但对于非浏览器的请求(如 Postman),这很有用
        res.status(500).send(‘CORS 错误: ‘ + err.message);
    }
});

const PORT = 5000;
app.listen(PORT, () => {
    console.log(`安全服务器正在运行在 ${PORT}`);
});

在这个配置中,我们使用了 INLINECODE02e0074a 函数。这是一种非常强大的模式。它允许我们在运行时动态决定是否允许某个请求。如果请求头中的 INLINECODE6fd3e758 在我们的白名单中,我们就调用 callback(null, true) 允许它;否则,我们抛出一个错误。

场景三:针对单个路由启用 CORS

有时,我们可能希望大多数 API 保持私有(只允许同源访问),但只对某一个特定的公共接口开放跨域访问。cors 中间件非常灵活,我们可以把它作为中间件参数传递给特定的路由,而不是全局挂载。

示例:

const express = require(‘express‘);
const cors = require(‘cors‘);
const app = express();

// 全局不启用 CORS
// 这意味着默认情况下,所有路由都是受同源策略保护的

// 公共接口:允许任何人访问
// 这个接口使用了 cors() 中间件
app.get(‘/public-data‘, cors(), (req, res) => {
    res.json({ 
        msg: "这是一个公共 API,任何人都可以跨域访问我!",
        data: [1, 2, 3] 
    });
});

// 私有接口:不允许跨域
// 这个接口没有使用 cors(),因此如果前端尝试跨域请求,浏览器会阻止它
app.get(‘/private-data‘, (req, res) => {
    res.json({ 
        msg: "这是一个私有 API,只有同源的前端才能访问。" 
    });
});

const PORT = 5000;
app.listen(PORT, () => {
    console.log(`服务器运行在端口 ${PORT}`);
});

这种“按需启用”的策略在微服务架构或混合应用中非常有用,可以最大程度地保证安全性。

场景四:处理带凭证的请求(Cookies)

这是一个非常容易踩坑的地方。如果你需要在跨域请求中发送 Cookies(例如使用 INLINECODEac855713 时设置 INLINECODE02e47b66),你需要进行额外的配置。

关键点:

  • 服务器的 CORS 配置中必须设置 credentials: true
  • 服务器的 INLINECODE05f7cf74 不能设置为通配符 INLINECODE629f5bb4,必须指定具体的域名(如上面提到的白名单数组)。

前端代码示例:

// 前端 fetch 必须带 credentials
fetch(‘http://localhost:5000/user-profile‘, {
    method: ‘GET‘,
    credentials: ‘include‘, // 关键:告诉浏览器要在请求中包含 Cookie
    headers: {
        ‘Content-Type‘: ‘application/json‘
    }
})
.then(res => res.json())
.then(data => console.log(‘用户数据:‘, data));

后端配置示例:

const corsOptions = {
    origin: ‘http://localhost:5500‘, // 必须指定具体源,不能用 ‘*‘
    credentials: true,               // 关键:允许凭证
    optionsSuccessStatus: 200
};

app.use(cors(corsOptions));

app.get(‘/user-profile‘, (req, res) => {
    // 这里的逻辑可以访问 req.cookies
    res.json({ username: "开发者123", loggedIn: true });
});

常见误区与故障排查

在开发过程中,即使配置了 CORS,你仍然可能遇到问题。以下是一些我们总结的经验和常见错误:

1. 预检请求(Preflight)失败

当你发送的请求不仅仅是简单的 GET 或 POST,或者你添加了自定义的 HTTP 头(如 INLINECODEd4053845),浏览器会先发送一个 INLINECODE9cf103bb 请求(称为“预检请求”),询问服务器是否允许该实际请求。

如果你的控制台报错 Request header field x-custom-header is not allowed by Access-Control-Allow-Headers,这意味着你需要配置允许的头部。

解决方案:

const corsOptions = {
    origin: ‘*‘,
    // 允许的请求头
    allowedHeaders: [‘Content-Type‘, ‘Authorization‘, ‘x-custom-token‘],
    // 暴露给客户端的响应头(允许客户端读取的响应头)
    exposedHeaders: ‘x-total-count‘, 
    // 允许的 HTTP 方法
    methods: ‘GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS‘
};

app.use(cors(corsOptions));

2. 代理方式绕过 CORS

在前端开发中(比如使用 Create React App 或 Vue CLI),我们可以配置开发服务器的代理。这实际上是将浏览器的请求发给了同源的开发服务器,再由开发服务器转发给后端。

package.json (前端项目) 中的配置示例:

"proxy": "http://localhost:5000"

这样,前端在请求 INLINECODE9fbfd1c5 时,实际上会被代理到 INLINECODE39d559c1。因为对浏览器来说,请求是发给同源的开发服务器的,所以不存在跨域问题。但这只适用于开发环境。

总结

在这篇文章中,我们一起深入探讨了 CORS 的方方面面。从理解“同源策略”开始,我们搭建了项目,实践了四种不同的配置场景:全局开放、白名单限制、单路由配置以及凭证处理。

CORS 并不神秘,它只是浏览器和服务器之间的一种约定。掌握了 Express 中的 cors 中间件配置,你就掌握了全栈开发连接前后端的关键钥匙。建议你在生产环境中始终使用白名单配置,并谨慎处理凭证,以确保应用的安全。

希望这篇文章能帮助你解决遇到的跨域难题。编码愉快!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/17775.html
点赞
0.00 平均评分 (0% 分数) - 0