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

CORS

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

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

res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Credentials', 'true'); // 允许服务器端发送Cookie数据

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

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

就像下面这样:

res.header('Access-Control-Allow-Origin', '*.666.com'); 

// 或者如下
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库的核心代码:

(function () {

 '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(','); // .headers is an array, 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, req));
 headers.push(configureCredentials(options, req));
 headers.push(configureExposedHeaders(options, req));
 applyHeaders(headers, 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, res, 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, req, res, 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字符串的验证就变成了相当于常规字符串的验证,我们不仅可以使用数组列表验证,还可以使用正则匹配。

具体代码如下:

// 判断origin是否在域名白名单列表中
function isOriginAllowed(origin, allowedOrigin) {
 if (_.isArray(allowedOrigin)) {
 for(let 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;
 }
}

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

app.post('a/b', function (req, res, 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,简直完美~

总结

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

(0)

相关推荐

  • nodeJS(express4.x)+vue(vue-cli)构建前后端分离实例(带跨域)

    准备工作: 1.安装nodejs ---还用我教了? 2.安装依赖包express4.x  点这里>>>nodeJS搭建本地服务器 3.安装vue-cli脚手架 点这里>>>vue-cli构建vue项目 这里强调一下,express是后端服务器,它是一个独立的服务器,vue启动的是前端服务器,vue-cli中已经集成了一个小型的express,这两个服务器是分开放的,但是它们都是基于nodejs的. nodeJS部分:这里我已经认为你搭建好了express服务器,并且能

  • 借助node实战JSONP跨域实例

    一.前言: 浏览器安全是基于同源策略的.所谓同源策略就是三相同: 1.协议相同: 2.域名相同: 3.端口相同. 但,凡事都是有利弊,同源策略也导致了我们想用AJAX跨域请求,但NO!!为了规避这种限制,其中有一方法就是JSONP. JSONP的基本思想:就是通过<script>的src,向服务器请求数据,且这不受同源策略限制(img和iframe的src也是如此):然后服务器将相应的数据放入指定的函数回调名中,返回给前端. 这,就实现了跨域请求信息. 如下图所示: 了解了JSONP的大体思路

  • Node.js配合node-http-proxy解决本地开发ajax跨域问题

    情景: 前后端分离,本地前端开发调用接口会有跨域问题,一般有以下3种解决方法: 1. 后端接口打包到本地运行(缺点:每次后端更新都要去测试服下一个更新包,还要在本地搭建java运行环境,麻烦) 2. CORS跨域:后端接口在返回的时候,在header中加入'Access-Control-Allow-origin':* 之类的(有的时候后端不方便这样处理,前端就蛋疼了) 3. 用nodejs搭建本地http服务器,并且判断访问接口URL时进行转发,完美解决本地开发时候的跨域问题.  用到的技术:

  • NODE.JS跨域问题的完美解决方案

    这几天公司同事(前端)写页面的时候一直说拿不到想要的JSON,安卓iOS那边是可以拿到的,但他也是新手也不知道为什么只知道是js跨域问题,然后问我我也不懂前端我开始百度, 有人说是谷歌浏览器跨域要设置一下,然后我就在谷歌浏览器的目标后面加一个 --disable-web-security 但是后来发现依然报错,依然拿不到想要的数据.后来也不停的找找找也没有什么眉目. 直到今天百度了一下PHP的跨域启发了我,于是百度找到了node.js的跨域问题,最后我在 app.js 路由设置里面加了一段跨域代

  • node跨域请求方法小结

    本文介绍了node跨域请求,主要介绍了两种方法,一种是jsonp,另一种res.wirteHead,具体如下: 第一种:jsonp 参看用nodejs实现json和jsonp服务 第二种:res.wirteHead node部分 var http = require('http') var url = require('url') var querystring = require('querystring') var port = 9000 var jsonData = { 'name': '

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

    CORS 说到CORS,相信前端儿都不陌生,这里我就不多说了,具体可以看看这篇文章. CORS,主要就是配置Response响应头中的 Access-Control-Allow-Origin 属性为你允许该接口访问的域名.最常见的设置是: res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Credentials', 'true'); // 允许服务器端发送Cookie数据 然而,这样的

  • 详解springboot设置cors跨域请求的两种方式

    1.第一种: public class CorsFilter extends OncePerRequestFilter { static final String ORIGIN = "Origin"; protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOE

  • Springboot处理CORS跨域请求的三种方法

    前言 Springboot跨域问题,是当前主流web开发人员都绕不开的难题.但我们首先要明确以下几点 跨域只存在于浏览器端,不存在于安卓/ios/Node.js/python/ java等其它环境 跨域请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了. 之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议.域名.端口号都完全一致. 浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的H

  • 详解javascript如何在跨域请求中携带cookie

    目录 1.搭建环境 2.测试同源cookie 3.跨域请求携带cookie 4.总结 5.知识点 1. 搭建环境 1.生成工程文件 npm init 2.安装 express npm i express --save 3.新增app1.js,开启服务器1 端口:3001 const express = require('express') const app = express() const port = 3001 // 设置`cookie` app.get("/login", (r

  • Springboot处理配置CORS跨域请求时碰到的坑

    最近开发过程中遇到了一个问题,之前没有太注意,这里记录一下.我用的SpringBoot版本是2.0.5,在跟前端联调的时候,有个请求因为用户权限不够就被拦截器拦截了,拦截器拦截之后打印日志然后response了一个错误返回了,但是前端Vue.js一直报如下跨域的错误,但是我是配置了跨域的. has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested res

  • 使用SpringCloudApiGateway之支持Cors跨域请求

    问题背景 公司的项目需要前后端分离,vue+java,这时候就需要支持Cors跨域请求了.最近对zuul进行升级,假如说zuul是1.0的话,api gateway就是2.0的网关,支持ws等,基于NIO,各方面还是强大的. 解决方案 新建一个Configuration类即可 import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.gateway.dis

  • 说说如何利用 Node.js 代理解决跨域问题

    前后端分离,经常会出现跨域访问被限制的问题. 跨域访问限制是服务端出于安全考虑的限制行为.即只有同域或者指定域的请求,才能访问.这样还可以防止图片被盗链.服务端(比如 Node.js)可以通过代理,来解决这一问题. 1 安装 request 库 npm install request --save-dev 2 配置 我们以知乎日报为例,配置两个代理.一个代理内容,另一个代理图片. 在项目根目录,配置 proxy.js : //代理 const http = require('http'); co

  • vue+springboot实现项目的CORS跨域请求

    跨域资源共享CORS(Cross-origin Resource Sharing),是W3C的一个标准,允许浏览器向跨源的服务器发起XMLHttpRequest请求,克服ajax请求只能同源使用的限制.关于CORS的详细解读,可参考阮一峰大神的博客:跨域资源共享CORS详解.本文为通过一个小demo对该博客中分析内容的一些验证. 1.springboot+vue项目的构建和启动 细节不在此赘述,任何简单的springboot项目就可以,而前端vue项目只需用axios发ajax请求即可. 我的d

  • Java实现CORS跨域请求的实现方法

    问题 使用前后端分离模式开发项目时,往往会遇到这样一个问题 -- 无法跨域获取服务端数据 这是由于浏览器的同源策略导致的,目的是为了安全.在前后端分离开发模式备受青睐的今天,前端和后台项目往往会在不同的环境下进行开发,这时就会出现跨域请求数据的需求,目前的解决方案主要有以下几种: JSONP.iframe.代理模式.CORS等等 前面几种方式在这里不讲,网上有很多资料.在这里我主要分享一下CORS这种解决方式,CORS即"跨域资源共享",它允许浏览器向跨源服务器,发出XMLHttpRe

  • js跨域请求数据的3种常用的方法

    由于js同源策略的影响,当在某一域名下请求其他域名,或者同一域名,不同端口下的url时,就会变成不被允许的跨域请求. 那这个时候通常怎么解决呢,对此菜鸟光头我稍作了整理: 1.JavaScript    在原生js(没有jQuery和ajax支持)的情况下,通常客户端代码是这样的(我假设是在localhost:8080的端口下的http://localhost:8080/webs/i.mediapower.mobi/wutao/index.html页面的body标签下面加入以下代码): <scr

随机推荐