在实践中深入了解跨域 + devServer.proxy

6,259 阅读4分钟

1. 背景

前段时间想在群里搞个钉钉机器人定时提醒自己写周报,然而又碰到了跨域的情况,代码大致如下:

-- index.js --

import axios from 'axios'
function sxx () {
    axios.post('https://oapi.dingtalk.com/robot/send?XXXXXXXX', {
        msgtype: 'text',
        text: {
          content: '[sxx]: 我就是我, 是不一样的烟火'
        }
      })
}
sxx ()

-- webpack.config.js --

const path = require('path')
module.exports = {
    entry: './index.js',
    devServer: {
        host: '0.0.0.0',
        port: 8080
    },
    plugins: [
      new HtmlWebpackPlugin()
    ],
    output: {
      filename: 'main.js',
      path: path.resolve(__dirname, './dist')
    }
}

报错如下所示:

2. 回顾跨域的知识

对于跨域,MDN里的这篇文章:HTTP访问控制(CORS)讲的很清晰。截取里面非常经典的图片(见下图),我们在domain-a.com网站下,访问domain-b.com的资源(png图片和eot的字体),这两个资源就会跨域,此时浏览器可能会阻止这两个资源请求的发送,也可能是跨站请求正常发送,但返回结果被浏览器拦截,具体要看浏览器的实现。

那么,对应于我们本例中的跨域情况,我们是在0.0.0.0:8080下,访问oapi.dingtalk.com的资源,由于该请求跨域,浏览器直接阻止该请求的发送

凭啥你说是阻止了请求的发送?
通过charles可以看到post请求https://oapi.dingtalk.com/robot/send压根都没有发送,只是一个connect请求(况且若请求真的发送了,群里就会有钉钉机器人的消息了,返回的结果对我来说并没用)

3. 更改origin是否可以处理跨域

处理跨域常见的就是cors,服务器端设置Access-Control-Allow-Origin即可,可咱总不能让钉钉来添加吧?从跨域的原理看来,浏览器就是通过判断请求头中的origin结合请求的url来判断是否跨域的,那咱是不是可以更改origin来骗过浏览器?此时,咱们也有必要再了解下请求头中的host,origin字段:HTTP headers 之 host/referer/origin。于是,代码更改如下: -- index.js --

import axios from 'axios'
function sxx () {
    axios.post('https://oapi.dingtalk.com/robot/send?XXXXXXXX', {
        msgtype: 'text',
        text: {
          content: '[sxx]: 我就是我, 是不一样的烟火'
        }
      }, { headers: {'Origin': 'oapi.dingtalk.com'} })
}
sxx ()

然而,直接报错了...主要看第一个报错:Refused to set unsafe header "Origin"

搜索下stack overflow大佬们对于该报错的解释,其中的第一条留言就很到位,出于安全性的考虑,header中的origin是人家浏览器设置的,咱们无法更改它(想想也是,要是人人都可以更改origin,浏览器的同源策略岂不形同虚设)

4. webpack的devServer.proxy处理了跨域

之前看到慕课网里dell老师讲的devServer.proxy可以代理开发环境中的url,尝试了下竟然绕过了跨域,请求成功了,代码更改如下:
-- index.js --

import axios from 'axios'
function sxx () {
    axios.post('/robot/send?XXXXXXXX', {
        msgtype: 'text',
        text: {
          content: '[sxx]: 我就是我, 是不一样的烟火'
        }
      })
}
sxx ()

-- webpack.config.js --

const path = require('path')
module.exports = {
    entry: './index.js',
    devServer: {
        host: '0.0.0.0',
        port: 8080,
        proxy: {
            '/robot': {
                target: 'https://oapi.dingtalk.com',
                secure: false, // 协议是https的时候必须要写
                changeOrigin: true
            }
        }
    },
    // ···省略了···太懒···
}

重点:添了changeOrigin: true后才可跨域,否则还是不行。那么重点就是理解为啥加上changeOrigin即可跨域

5. changeOrigin如何解决跨域

dell老师在视频里说changeOrigin字段是为了防止网站被爬数据,会验证请求的origin,若origin不是本网站的,则请求无法获得结果,看起来像是改变了header里的origin,但是刚不是尝试了咱们是无法更改origin的么?
查看文档中关于changeOrigin字段的描述:

changeOrigin: change the origin of the host header to the target url

还是不太明白,然后查看源码(在http-proxy/common.js中):

从源码里可以清晰的看到,设置了changeOrigin只是更改了request请求中的host,并不是origin,那么更加奇怪了,到底是如何绕过跨域的呢?

其实,devServer中的proxy就相当于charles进行url的代理,在sxx()执行后发送的请求是http://0.0.0.0:8080/robot/send?XXXXXXXX,我们是在0.0.0.0:8080下,当然不会限制这样的请求的发送,然后devServer的proxy通过配置将host更改为oapi.dingtalk.com,该请求就能正常进行,大致情况如下图所示:

6. charles模拟

为验证该想法,使用charles替换devServer.proxy进行url的代理,对于http://0.0.0.0:8080/robot/send?XXXXXXXX进行Breakpoint,更改该请求的host。 但是,又报错啦...如下图

查询stack overflow中‘invalid Host header’,找到解决方法:只要在devServer中加个disableHostCheck: true即可。