Webpack配置解决跨域和Set-Cookie失效问题

5,887 阅读5分钟

什么是跨域

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

当前页面url被请求页面url是否跨域原因
www.test.com/www.test.com/index.html同源(协议、域名、端口号相同)
www.test.com/www.test.com/index.html跨域协议不同(http/https)
www.test.com/www.baidu.com/跨域主域名不同(test/baidu)
www.test.com/blog.test.com/跨域子域名不同(www/blog)
www.test.com:8080/www.test.com:7001/跨域端口号不同(8080/7001)

跨域问题

跨域问题的具体体现在浏览器上开发页面时,跨域的请求会被浏览器拦截。

出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

请求被拦截是浏览器对跨域的处理,并非服务端对跨域的处理。某些用于开发的浏览器可以不遵守同源策略,从而无跨域问题。

跨域解决方法

Webpack代理服务器

如果是开发时导致的跨域,最好的解决方法是设置代理服务器。现代开发基于Webpack

我们先简单提及一下webpack-dev-server这个插件的原理,本地启动了一个使用express的Http服务器

并监听一个端口,这个本地服务器与客户端采用websocket通信协议,当原始文件发生改变,webpack-dev-server会实时编译。上线是请求实际的服务器,开发时是请求本地的服务器。

服务器不仅可以提供热更新的功能,还可以充当代理服务器

配置:

module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://www.baidu.com/',
        changeOrigin: true,     // target是域名的话,需要这个参数,
        pathRewrite: {'^/api' : ''}
      },
      '/api2': {
          .....
      }
    }
  }
};

参数说明

  • '/api'和pathRewrite

    '/api'是捕获API的标志,如果API中有这个字符串,那么就开始匹配代理,比如API请求/api/users, 会被代理到请求 www.baidu.com/api/users

    pathRewrite作用是把路径重写,也就是说会修改最终请求的API路径。比如访问的刚刚未设置pathRewrite的API路径:http://www.baidu.com/api/users,设置pathRewrite: {'^/api' : ''},后,最终代理访问的路径:http://www.baidu.com/users,这个参数的目的是给代理命名后,在访问时把命名删除掉。

  • target和changeOrigin

    代理的API地址,就是需要跨域的API地址。地址可以是域名,如:http://www.baidu.com,也可以是IP地址:http://127.0.0.1:3000。如果是域名需要额外添加一个参数changeOrigin: true,否则会代理失败。

更多请看文档

当我们配置proxy之后,发送请求应该往本地端口发,比如原根地址是 www.baidu.com ,现在根地址就应该变为 /api。因为发送的请求和url同源,自然不会有跨域问题了,本质是本地服务器代理请求以达到欺骗浏览器的目的。

代理服务器Cookie失效的问题

然而在实际开发中,我们有时会发现设置上述的代理后,请求可以发出去了但浏览器无法通过返回头的Set-Cookie字段设置Cookie。

简单来了解一下Set-Cookie字段

属性说明
NAME=VALUE赋予 Cookie 的名称和其值(必需项)
expires=DATECookie 的有效期(若不明确指定则默认为浏 览器关闭前为止)
path=PATH将服务器上的文件目录作为Cookie的适用对象(若不指定则默认为文档所在的文件目录)
domin=域名作为 Cookie 适用对象的域名 (若不指定则 默认为创建 Cookie 的服务器的域名)
Secure仅在 HTTPS 安全通信时才会发送 Cookie
HttpOnly加以限制,使 Cookie 不能被 JavaScript 脚 本访问

非同源情况我们已经排除了,那问题只可能出现在Cookie不适用于path,domin和Secure这三项。

例如 后台考虑Cookie的适用范围时,就可能会加上path条件。

比如我的项目上线存放html的地址是 https://xxx/content,我的后台就设置了 path=/content。而开发请求的地址默认是根目录,即 http://localhost:xxxx ,不在Cookie的适用范围。解决方法有多种:

  • 后台判断开发环境进行设置

  • 更改根地址,加上content

  • 暴力处理Set-Cookie字段

    上面的proxy设置中有 option.cookiePathRewriteoption.cookieDomainRewrite的选项,作用是重写Set-Cookie中的path和domain条件,还有一种更为暴力的方法,proxy可以直接对返回头进行处理

proxy的onProxyRes函数

module.exports = {
  // ...
  devServer: {
    proxy: {
      '/api': {
        // ...
        onProxyRes: function(proxyRes, req, res) {
          const cookies = proxyRes.headers['set-cookie']
          const cookiePathRegex = /(p|P)ath=\/\w*;/
          let newCookie
          // 修改cookie Path
          if (cookies) {
            newCookie = cookies.map(cookie => {
              if (cookieRegex.test(cookie)) {
                // 替换
                return cookie.replace(cookiePathRegex, 'path=/;')
              }
              return cookie
            })
            // 替换set-cookie
            delete proxyRes.headers['set-cookie']
            proxyRes.headers['set-cookie'] = newCookie
            // console.log(proxyRes.headers['set-cookie'])
          }
        }
      },
    }
  }
};

onProxyRes中我把path改为'path=/',就不存在限制了。因为可以把set-cookie完全重写,理论上可以处理任何cookie不适用的问题。具体处理看返回头,有的设置Cookie是set-cookie字段而不是是Set-Cookie字段,有的是因为domin限制而不是path限制。