Node.js设置CORS跨域请求中多域名白名单的方法

CORS

说到CORS,相信前端儿都不陌生,这里我就不多说了,具体可以看看

CORS,主要就是配置Response响应头中的 Access-Control-Allow-Origin 属性为你允许该接口访问的域名。最常见的设置是:

然而,这样的设置是最简单粗暴,同时也是最不安全的。它表示该接口允许所有的域名对它进行跨域请求。然而,在一般实际业务中,都希望该接口只允许对某一个或几个网站开放跨域请求权限,而非全部。

那么,聪明的你肯定想着,多域名白名单还不简单吗,写个正则就好啦?再不行,直接配置 Access-Control-Allow-Origin 属性为用逗号分隔的多个域名不就好了吗?

就像下面这样:

// 或者如下
res.header('Access-Control-Allow-Origin','a.666.com,b.666.com,c.666.com');

很遗憾地告诉你,这样的写法是无效的。在Node.js中,res的响应头Header中的 Access-Control-Allow-Origin 属性不能匹配除 (*) 以外的正则表达式的,域名之间不能也用逗号分隔。也就是说, Access-Control-Allow-Origin 的属性值只允许设置为单个确定域名字符串或者 (*)。

既然我们希望允许的是多个域名,也不愿意使用不安全的 * 通配符,难道就真不能配置多域名白名单的CORS了吗?

多域名白名单的CORS确实是可以实现的。只是有一点曲线救国的味道。

多域名白名单的CORS实现原理

具体原理可以参考cors库的核心代码:

'use strict';

var assign = require('object-assign');
var vary = require('vary');

var defaults = {
origin: '*',methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',preflightContinue: false,optionsSuccessStatus: 204
};

function isString(s) {
return typeof s === 'string' || s instanceof String;
}

function isOriginAllowed(origin,allowedOrigin) {
if (Array.isArray(allowedOrigin)) {
for (var i = 0; i < allowedOrigin.length; ++i) {
if (isOriginAllowed(origin,allowedOrigin[i])) {
return true;
}
}
return false;
} else if (isString(allowedOrigin)) {
return origin === allowedOrigin;
} else if (allowedOrigin instanceof RegExp) {
return allowedOrigin.test(origin);
} else {
return !!allowedOrigin;
}
}

function configureOrigin(options,req) {
var requestOrigin = req.headers.origin,headers = [],isAllowed;

if (!options.origin || options.origin === '') {
// allow any origin
headers.push([{
key: 'Access-Control-Allow-Origin',value: '
'
}]);
} else if (isString(options.origin)) {
// fixed origin
headers.push([{
key: 'Access-Control-Allow-Origin',value: options.origin
}]);
headers.push([{
key: 'Vary',value: 'Origin'
}]);
} else {
isAllowed = isOriginAllowed(requestOrigin,options.origin);
// reflect origin
headers.push([{
key: 'Access-Control-Allow-Origin',value: isAllowed ? requestOrigin : false
}]);
headers.push([{
key: 'Vary',value: 'Origin'
}]);
}

return headers;
}

function configureMethods(options) {
var methods = options.methods;
if (methods.join) {
methods = options.methods.join(','); // .methods is an array,so turn it into a string
}
return {
key: 'Access-Control-Allow-Methods',value: methods
};
}

function configureCredentials(options) {
if (options.credentials === true) {
return {
key: 'Access-Control-Allow-Credentials',value: 'true'
};
}
return null;
}

function configureAllowedHeaders(options,req) {
var allowedHeaders = options.allowedHeaders || options.headers;
var headers = [];

if (!allowedHeaders) {
allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified,so reflect the request headers
headers.push([{
key: 'Vary',value: 'Access-Control-Request-Headers'
}]);
} else if (allowedHeaders.join) {
allowedHeaders = allowedHeaders.join(','); // .headers is an array,so turn it into a string
}
if (allowedHeaders && allowedHeaders.length) {
headers.push([{
key: 'Access-Control-Allow-Headers',value: allowedHeaders
}]);
}

return headers;
}

function configureExposedHeaders(options) {
var headers = options.exposedHeaders;
if (!headers) {
return null;
} else if (headers.join) {
headers = headers.join(',so turn it into a string
}
if (headers && headers.length) {
return {
key: 'Access-Control-Expose-Headers',value: headers
};
}
return null;
}

function configureMaxAge(options) {
var maxAge = options.maxAge && options.maxAge.toString();
if (maxAge && maxAge.length) {
return {
key: 'Access-Control-Max-Age',value: maxAge
};
}
return null;
}

function applyHeaders(headers,res) {
for (var i = 0,n = headers.length; i < n; i++) {
var header = headers[i];
if (header) {
if (Array.isArray(header)) {
applyHeaders(header,res);
} else if (header.key === 'Vary' && header.value) {
vary(res,header.value);
} else if (header.value) {
res.setHeader(header.key,header.value);
}
}
}
}

function cors(options,req,res,next) {
var headers = [],method = req.method && req.method.toUpperCase && req.method.toUpperCase();

if (method === 'OPTIONS') {
// preflight
headers.push(configureOrigin(options,req));
headers.push(configureCredentials(options,req));
headers.push(configureMethods(options,req));
headers.push(configureAllowedHeaders(options,req));
headers.push(configureMaxAge(options,req));
headers.push(configureExposedHeaders(options,req));
applyHeaders(headers,res);

if (options.preflightContinue ) {
next();
} else {
res.statusCode = options.optionsSuccessStatus || defaults.optionsSuccessStatus;
res.end();
}
} else {
// actual response
headers.push(configureOrigin(options,res);
next();
}
}

function middlewareWrapper(o) {
if (typeof o !== 'function') {
o = assign({},defaults,o);
}

// if options are static (either via defaults or custom options passed in),wrap in a function
var optionsCallback = null;
if (typeof o === 'function') {
optionsCallback = o;
} else {
optionsCallback = function (req,cb) {
cb(null,o);
};
}

return function corsMiddleware(req,next) {
optionsCallback(req,function (err,options) {
if (err) {
next(err);
} else {
var originCallback = null;
if (options.origin && typeof options.origin === 'function') {
originCallback = options.origin;
} else if (options.origin) {
originCallback = function (origin,cb) {
cb(null,options.origin);
};
}

if (originCallback) {
originCallback(req.headers.origin,function (err2,origin) {
if (err2 || !origin) {
next(err2);
} else {
var corsOptions = Object.create(options);
corsOptions.origin = origin;
cors(corsOptions,next);
}
});
} else {
next();
}
}
});
};
}

// can pass either an options hash,an options delegate,or nothing
module.exports = middlewareWrapper;

}());

实现原理是这样的:

既然 Access-Control-Allow-Origin 属性已经明确不能设置多个域名,那么我们只得放弃这条路了。

最流行也是最有效的方法就是,在服务器端判断请求的Header中Origin属性值(req.header.origin)是否在我们的域名白名单列表内。如果在白名单列表内,那么我们就把 Access-Control-Allow-Origin 设置成当前的Origin值,这样就满足了Access-Control-Allow-Origin 的单一域名要求,也能确保当前请求通过访问;如果不在白名单列表内,则返回错误信息。

这样,我们就把跨域请求的验证,从浏览器端转移到服务端来了。对Origin字符串的验证就变成了相当于常规字符串的验证,我们不仅可以使用数组列表验证,还可以使用正则匹配。

具体代码如下:

const ALLOW_ORIGIN = [ // 域名白名单
'.233.666.com','hello.world.com','hello...com'
];

app.post('a/b',function (req,next) {
let reqOrigin = req.headers.origin; // request响应头的origin属性

// 判断请求是否在域名白名单内
if(isOriginAllowed(reqOrigin,ALLOW_ORIGIN)) {
// 设置CORS为请求的Origin值
res.header("Access-Control-Allow-Origin",reqOrigin);
res.header('Access-Control-Allow-Credentials','true');

// 你的业务代码逻辑代码 ...
// ...
} else {
res.send({ code: -2,msg: '非法请求' });
}
});

Oh yeah,简直完美~

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对编程之家的支持。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要介绍“基于nodejs的ssh2怎么实现自动化部署”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“基于nodejs...
本文小编为大家详细介绍“nodejs怎么实现目录不存在自动创建”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs怎么实现目录不存在自动创建”文章能帮助大...
这篇“如何把nodejs数据传到前端”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这...
本文小编为大家详细介绍“nodejs如何实现定时删除文件”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs如何实现定时删除文件”文章能帮助大家解决疑惑...
这篇文章主要讲解了“nodejs安装模块卡住不动怎么解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来...
今天小编给大家分享一下如何检测nodejs有没有安装成功的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文...
本篇内容主要讲解“怎么安装Node.js的旧版本”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎...
这篇“node中的Express框架怎么安装使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家...
这篇文章主要介绍“nodejs如何实现搜索引擎”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“nodejs如何实现搜索引擎...
这篇文章主要介绍“nodejs中间层如何设置”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“nodejs中间层如何设置”文...
这篇文章主要介绍“nodejs多线程怎么实现”,在日常操作中,相信很多人在nodejs多线程怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
这篇文章主要讲解了“nodejs怎么分布式”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“nodejs怎么分布式”...
本篇内容介绍了“nodejs字符串怎么转换为数组”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情...
这篇文章主要介绍了nodejs如何运行在php服务器的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇nodejs如何运行在php服务器文章都...
本篇内容主要讲解“nodejs单线程如何处理事件”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“nodejs单线程如何...
这篇文章主要介绍“nodejs怎么安装ws模块”,在日常操作中,相信很多人在nodejs怎么安装ws模块问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法...
本篇内容介绍了“怎么打包nodejs代码”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!
本文小编为大家详细介绍“nodejs接收到的汉字乱码怎么解决”,内容详细,步骤清晰,细节处理妥当,希望这篇“nodejs接收到的汉字乱码怎么解决”文章能帮助大家解...
这篇“nodejs怎么同步删除文件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇...
今天小编给大家分享一下nodejs怎么设置淘宝镜像的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希