使用Node.js处理CORS

介绍

在本文中,我们将研究什么是CORS,如何使用Express配置CORS,以及如何根据需要定制CORS中间件。

什么是CORS

CORS跨域资源共享的简写。它是一种机制,允许或限制Web服务器上请求的资源,具体取决于启动HTTP请求的位置。

此策略用于保护特定Web服务器免受其他网站或域的访问。例如,只有允许的域才能访问服务器中的托管文件,例如样式表,图像或脚本。

如果您当前正在使用http://example.com/page1并且要从中引用图像http://image.com/myimage.jpg,则除非http://image.com允许使用进行跨域共享,否则http://example.com将无法获取该图像。

origin每个HTTP请求中都有一个HTTP标头。它定义了域请求的来源。我们可以使用标头信息来限制或允许来自Web服务器的资源来保护它们。

默认情况下,来自任何其他来源的请求都将受到浏览器的限制。

例如,当您仍处于开发阶段时-如果您使用的是诸如React之类的前端库,则将在上使用您的前端应用程序http://localhost:3000。同时,您的Express服务器可能正在其他端口上运行,例如http://localhost:2020

因此,您需要在这些服务器之间允许CORS。

如果您在浏览器控制台中看到此常见错误。CORS限制可能是问题所在:

当您提供公共API并希望控制对某些资源的访问以及人们的使用方式时,CORS确实很有用。

另外,如果您想在其他网页上使用自己的API或文件,则可以简单地将CORS配置为允许这样做,同时仍将其他人拒之门外。

使用Express配置CORS

让我们从一个新的项目开始。我们将为其创建一个目录,输入目录并npm init使用默认设置运行:

$ mkdir myapp
$ cd myapp
$ npm init -y

然后,让我们安装所需的模块。我们将使用expresscors中间件:

$ npm i --save express
$ npm i --save cors

然后,让我们开始创建一个具有两个路由的快速Web应用程序,以演示CORS的工作原理。

我们将创建一个称为index.jsWeb服务器的文件,其中包含几个请求处理程序:

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

const app = express();

app.get('/', (req, res) => {
    res.json({
        message: 'Hello World'
    });
});

app.get('/:name', (req, res) => {
    let name = req.params.name;

    res.json({
        message: `Hello ${name}`
    });
});

app.listen(2020, () => {
    console.log('server is listening on port 2020');
});

让我们运行应用程序和服务器:

$ node index.js

现在,如果您转到http://localhost:2020/-服务器应返回JSON消息:

{
  "message": "Hello World"
}

另外,如果您http://localhost:2020/something应该会看到:

{
  "message": "Hello something"
}

启用所有CORS请求

如果要为所有请求启用CORS,则只需cors在配置路由之前使用中间件即可:

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

const app = express();

app.use(cors())

......

如果需要,这将允许所有路由都可以在网络上的任何位置访问。因此,在我们的示例中,每个域都可以访问这两个路由。

例如,如果我们的服务器正在运行http://www.example.com并提供诸如图像之类的内容,则我们允许其他域http://www.differentdomain.com引用诸如http://www.example.com中的内容。

因此,上的网页http://www.differentdomain.com可以将我们的域用作图像的来源:

<img src="http://www.example.com/img/cat.jpg">

为单个路由启用CORS

但是,如果您需要某个路由而不是其他路由,则可以cors在某个路由中将其配置为中间件,而不是将其配置为整个应用程序:

app.get('/', cors(), (req, res) => {
    res.json({
        message: 'Hello World'
    });
});

这将允许任何域都可以访问特定路由。因此,在您的情况下,/每个域都只能访问该路由。/:name只有在与API相同的域中发起的请求(http://localhost:2020在我们的示例中)才可以访问该路由。

例如,如果您尝试将获取请求/从其他来源发送到路径-它将成功,并且您将获得Hello World消息作为响应:

fetch('http://localhost:2020/')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(err => console.error(err));

如果运行以下代码,您应该看到来自服务器的响应已成功登录到控制台:

{
    message: 'Hello World'
}

但是,如果您尝试访问除根路径之外的其他任何路径,例如http://localhost:2020/namehttp://localhost:2020/img/cat.png,则此请求将被浏览器阻止:

fetch('http://localhost:2020/name/janith')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

如果您尝试在其他Web应用程序中运行此代码,则应该看到以下错误:

使用选项配置CORS

您还可以将配置选项与CORS结合使用以进一步自定义此选项。您可以使用配置来允许单个域或子域访问,可以配置允许的HTTP方法,例如GETPOST根据您的要求。

您可以通过以下方式使用CORS选项允许单个域访问:

var corsOptions = {
    origin: 'http://localhost:8080',
    optionsSuccessStatus: 200 // For legacy browser support
}

app.use(cors(corsOptions));

如果您在源中配置域名-服务器将允许来自已配置域的CORS。因此,http://localhost:8080在我们的情况下,将可以访问该API ,并禁止其他域使用。

如果我们发送GET请求,则访问任何路径都应该可行,因为这些选项是在应用程序级别而不是功能级别应用的。

因此,如果我们运行以下代码并从发送请求http://localhost:8080http://localhost:2020

fetch('http://localhost:2020/')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

// Or

fetch('http://localhost:2020/name/janith')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => console.error(err));

我们被允许从该应用程序和域中获取信息。

您还可以根据需要配置允许的HTTP方法:

var corsOptions = {
    origin: 'http://localhost:8080',
    optionsSuccessStatus: 200 // For legacy browser support
    methods: "GET, PUT"
}

app.use(cors(corsOptions));

如果我们POST从发送请求http://localhost:8080,则该请求只会被浏览器阻止,GET并且PUT受支持:

fetch('http://localhost:2020', {
  method: 'POST',
  body: JSON.stringify({name: "janith"}),
})
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.error(err));

使用功能配置动态CORS起源

如果配置不满足您的要求,则可以创建功能来定制CORS。

例如,假设您要允许CORS共享.jpg文件http://something.comhttp://example.com

const allowlist = ['http://something.com', 'http://example.com'];

    const corsOptionsDelegate = (req, callback) => {
    let corsOptions;

    let isDomainAllowed = whitelist.indexOf(req.header('Origin')) !== -1;
    let isExtensionAllowed = req.path.endsWith('.jpg');

    if (isDomainAllowed && isExtensionAllowed) {
        // Enable CORS for this request
        corsOptions = { origin: true }
    } else {
        // Disable CORS for this request
        corsOptions = { origin: false }
    }
    callback(null, corsOptions)
}

app.use(cors(corsOptionsDelegate));

回调函数将接受两个参数。第一个是我们通过的错误,null第二个是我们通过的选项{ origin: false }。第二个参数可能是使用requestExpress请求处理程序中的对象构造的许多选项。

因此,在自定义功能中配置的,托管在Web应用程序上http://something.comhttp://example.com能够.jpg从服务器引用带有扩展名的图像的Web应用程序。

因此,以下任何一个都将成功完成以下图像附件:

<img src="http://yourdomain.com/img/cat.jpg">

但是以下附件将被阻止:

<img src="http://yourdomain.com/img/cat.png">

从数据源加载允许的来源清单

您还可以使用数据库中允许的域的列表或使用任何后备数据源来允许CORS:

var corsOptions = {
    origin: function (origin, callback) {
        // Loading a list of allowed origins from the database
        // Ex.. origins = ['http://example.com', 'http//something.com']
        database.loadOrigins((error, origins) => {
            callback(error, origins);
        });
    }
}

app.use(cors(corsOptions));

结论

在本文中,我们介绍了CORS是什么以及如何使用Express对其进行配置。然后,我们为所有请求,特定请求,添加的选项和限制设置了CORS,并为动态CORS配置定义了自定义功能。

参考

Handling CORS with Node.js

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容