• 技术文章 >web前端 >js教程

    Node.js设置CORS跨域请求中多域名白名单的示例代码分享

    黄舟黄舟2017-03-28 14:34:18原创1421
    这篇文章主要介绍了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,简直完美~

    总结

    以上就是Node.js设置CORS跨域请求中多域名白名单的示例代码分享的详细内容,更多请关注php中文网其它相关文章!

    声明:本文原创发布php中文网,转载请注明出处,感谢您的尊重!如有疑问,请联系admin@php.cn处理
    上一篇:nodejs模块nodemailer基本使用-支持附件的邮件发送示例代码(图) 下一篇:Vue.js快速入门的图文代码详解
    大前端线上培训班

    相关文章推荐

    • 聊聊TypeScript中unknown和any的差异• 浅谈如何手动配置 node_modules 中的依赖包• 手把手教你使用工具切换 node 版本• javascript怎么设置字体大小• javascript暂停功能如何实现

    全部评论我要评论

  • 取消发布评论发送
  • 1/1

    PHP中文网