webpack-dev-server 代理解决cookie丢失问题--cookiePathRewrite

问题

最近项目对接口进行安全改造,须要用到一个Path=/XXX/的cookie值,可是本地开发环境会出现cookie丢失的问题,由于本地开发环境目录都是http://localhost:8000/home,不会包含XXX路径,这样请求就会丢失用于安全的cookie.html

解决方案

最简单粗暴的解决方案

修改项目目录,添加一个XXX的文件夹,把开发环境的须要的静态资源和页面文件放到XXX文件夹下,开发环境下访问项目地址改为http://localhost:8485/XXX。显然这个方案有缺陷,若是cookie path 改变,咱们又须要再次改变项目目录结构,可能还须要修改webpack配置(或者其余打包配置)node

nginx 代理的方式

若是项目本地开发环境使用了nginx代理,那么只须要一行配置就能够轻松搞定,直接上代码webpack

location /{
    ...
    proxy_pass http://localhost:8000;
    proxy_cookie_path /XXX/ /;
    ...
}
 
#原理是代理转换了cookie的path,从/XXX/,转换成/。这样项目就不用作任何修改了。
复制代码

webpack-dev-server 解决方案

了解前面两个方案以后,咱们来看看重头戏,项目没有使用nginx做为代理,而是使用webpack-dev-server(^2.4.5)提供的代理功能,咱们改怎么来配置呢?相信比较熟悉webpack-dev-server的同窗都知道webpack-dev-server能够配置proxy,其实就是个代理的配置。先看一下最终的解决方案,在webpack.config.js中配置,以下nginx

devServer:{
    proxy: {
      "/api": {
        target: "http://localhost:8000",
        pathRewrite: {"^/api" : ""}
      },
      onProxyRes: function(proxyRes, req, res) {
          var cookies = proxyRes.headers['set-cookie'];
          var cookieRegex = /Path=\/XXX\//i;
          //修改cookie Path
          if (cookies) {
            var newCookie = cookies.map(function(cookie) {
              if (cookieRegex.test(cookie)) {
                return cookie.replace(cookieRegex, 'Path=/');
              }
              return cookie;
            });
            //修改cookie path
            delete proxyRes.headers['set-cookie'];
            proxyRes.headers['set-cookie'] = newCookie;
          }
        }
    }
}

复制代码

因为查找了不少资料也没有查到简单的配置方式,我使用了onProxyRes的配置进行手动修改cookie。若是其余同窗有其余简单一些的方式,还望不吝赐教!git

首先,一样是做为代理,个人思路就是参照nignx的思路同样,对cookie 的path进行一个转化,这样思路就明确了,查找配置,转换cookie,我感受已经离胜利很近啦。github

果真我仍是太年轻啊,觉得剩下的事情确定so easy了,结果我看了好几遍官网文档中proxy配置项,一个一个地查看,压根找不到那一项配置能够修改cookie 的path;而后我开始寻求百度,google的帮助,就这样查了半天,密密麻麻的浏览器标签,泪崩,难道真没办法了?仍是大神们历来不这么玩啊。。。web

以后看到官网有一句话“The dev-server makes use of the powerful http-proxy-middleware package. Checkout its documentation for more advanced usages.”,茅塞顿开啊,原来更高级的使用方式能够去查看http-proxy-middleware,完整的配置你们能够自行查看和学习正则表达式

此处仅仅介绍几个配置api

  • cookieDomainRewrite浏览器

    这个配置能够重写cookie 的domain,当看到这个配置时,眼睛都亮了,按理说也该有个cookiePathRewrite,我确认了好几遍,确实没有。

  • onProxyReq 代理请求事件,能够在这里对请求修改。

  • onProxyRes

    代理响应事件,能够在这里修改响应。

function onProxyRes(proxyRes, req, res) {
    proxyRes.headers['x-added'] = 'foobar';     // add new header to response
    delete proxyRes.headers['x-removed'];       // remove header from response
}
复制代码

重点来了,看到github上的这段demo,思路就有了,利用这个事件回调咱们能够对set-cookie响应头进行重写,替换Path值。再贴一遍代码:

onProxyRes: function(proxyRes, req, res) {
          var cookies = proxyRes.headers['set-cookie'];
          var cookieRegex = /Path=\/XXX\//i;
          //修改cookie Path
          if (cookies) {
            var newCookie = cookies.map(function(cookie) {
              if (cookieRegex.test(cookie)) {
                return cookie.replace(cookieRegex, 'Path=/');
              }
              return cookie;
            });
            //修改cookie path
            delete proxyRes.headers['set-cookie'];
            proxyRes.headers['set-cookie'] = newCookie;
          }
        }
复制代码

此处使用了proxyRes对象进行操做,遍历proxyRes.headers['set-cookie'],替换相应的Path值,删除原来的set-cookie,再从新设置一遍便可。

至此,咱们能够在webpack-dev-server的proxy中来进行配置,解决开发环境下cookie丢失的问题。

==2018-07-25补充==

最新的webpack-dev-server3.1.5已经支持配置cookiePathRewrite,依赖于http-proxy的更新

cookiePathRewrite: {
  "/unchanged.path/": "/unchanged.path/",
  "/old.path/": "/new.path/",
  "*": ""
}
复制代码

http-proxy最新版1.17.0支持cookiePathRewrite配置修改cookie路径,而http-proxy-middleware最新版本也更新了依赖的http-proxy版本;咱们查看webpack-dev-server3.1.5依赖的http-proxy-middleware的版本也是最新版0.18.0

因此如今修改cookie的path更简单了:

devServer:{
    proxy:{
        cookiePathRewrite:{
            "/old.path/": "/new.path/",
        }
    }
}
复制代码

那么http-proxy中是怎么改写cookie path的呢?直接贴源码

/**
   * Copy headers from proxyResponse to response
   * set each header in response object.
   *
   * @param {ClientRequest} Req Request object
   * @param {IncomingMessage} Res Response object
   * @param {proxyResponse} Res Response object from the proxy request
   * @param {Object} Options options.cookieDomainRewrite: Config to rewrite cookie domain
   *
   * @api private
   */
  writeHeaders: function writeHeaders(req, res, proxyRes, options) {
    var rewriteCookieDomainConfig = options.cookieDomainRewrite,
        rewriteCookiePathConfig = options.cookiePathRewrite,
        preserveHeaderKeyCase = options.preserveHeaderKeyCase,
        rawHeaderKeyMap,
        setHeader = function(key, header) {
          if (header == undefined) return;
          if (rewriteCookieDomainConfig && key.toLowerCase() === 'set-cookie') {
            header = common.rewriteCookieProperty(header, rewriteCookieDomainConfig, 'domain');
          }
          if (rewriteCookiePathConfig && key.toLowerCase() === 'set-cookie') {
            header = common.rewriteCookieProperty(header, rewriteCookiePathConfig, 'path');
          }
          res.setHeader(String(key).trim(), header);
        };

    if (typeof rewriteCookieDomainConfig === 'string') { //also test for ''
      rewriteCookieDomainConfig = { '*': rewriteCookieDomainConfig };
    }

    if (typeof rewriteCookiePathConfig === 'string') { //also test for ''
      rewriteCookiePathConfig = { '*': rewriteCookiePathConfig };
    }

    // message.rawHeaders is added in: v0.11.6
    // https://nodejs.org/api/http.html#http_message_rawheaders
    if (preserveHeaderKeyCase && proxyRes.rawHeaders != undefined) {
      rawHeaderKeyMap = {};
      for (var i = 0; i < proxyRes.rawHeaders.length; i += 2) {
        var key = proxyRes.rawHeaders[i];
        rawHeaderKeyMap[key.toLowerCase()] = key;
      }
    }

    Object.keys(proxyRes.headers).forEach(function(key) {
      var header = proxyRes.headers[key];
      if (preserveHeaderKeyCase && rawHeaderKeyMap) {
        key = rawHeaderKeyMap[key] || key;
      }
      setHeader(key, header);
    });
  },

复制代码

这段代码就是把proxyRes的headers复制到response中,从源码中能够看到此处会根据配置进行cookie的重写,包括对domain 和path的重写,其实就是遍历headers时,找到set-cookie的header,调用common.rewriteCookieProperty重写cookie的path。 如下是rewriteCookieProperty源码:

/**
 * Rewrites or removes the domain of a cookie header
 *
 * @param {String|Array} Header
 * @param {Object} Config, mapping of domain to rewritten domain.
 *                 '*' key to match any domain, null value to remove the domain.
 *
 * @api private
 */
common.rewriteCookieProperty = function rewriteCookieProperty(header, config, property) {
  if (Array.isArray(header)) {
    return header.map(function (headerElement) {
      return rewriteCookieProperty(headerElement, config, property);
    });
  }
  return header.replace(new RegExp("(;\\s*" + property + "=)([^;]+)", 'i'), function(match, prefix, previousValue) {
    var newValue;
    if (previousValue in config) {
      newValue = config[previousValue];
    } else if ('*' in config) {
      newValue = config['*'];
    } else {
      //no match, return previous value
      return match;
    }
    if (newValue) {
      //replace value
      return prefix + newValue;
    } else {
      //remove value
      return '';
    }
  });
};
复制代码

核心就是根据传入的property构建正则表达式new RegExp("(;\s*" + property + "=)([^;]+)", 'i'),用字符串的replace方法替换。

参考:

https://github.com/nodejitsu/node-http-proxy

https://github.com/chimurai/http-proxy-middleware

相关文章
相关标签/搜索