基于Chrome插件实现支持CORS的本地开发代理

10,273 阅读4分钟

什么是跨域

跨域:由于浏览器的同源策略,属于不同域的页面之间不能相互访问各自的页面内容。

开发中后台项目,我们首先需要让本地服务run起来,起一个如http://localhost:8000/scg/show这样的页面,然后,当我们需要进行网络交互时,通常使用相对域名,如/scg/search.json?pageNo=1, 假设项目的代理配置如下

undefined

那么当请求/scg/search.json?pageNo=1时,代理转发出去的请求的是http://ottscg.alibaba.net/scg/search.json?pageNo=1, localhost去请求ottscg.alibaba.net中的内容,这时就发生了跨域。

CORS跨域发送 Cookie

浏览器通过代理发出CORS请求时,会在头信息之中,增加一个Origin字段,表示本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。

如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个以"Access-Control"开头的头信息字段,为了发送带cookie的跨域请求,我们首先需要关心的是Access-Control-Allow-OriginAccess-Control-Allow-Credentials

Access-Control-Allow-Origin

这个头是允许CORS时,必须返回的头,它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

Access-Control-Allow-Credentials

默认情况下浏览器对跨域请求不会携带 Cookie,但鉴于 Cookie 在身份验证等方面的重要性, CORS 推荐使用额外的响应头字段来允许跨域发送 Cookie。 这是一个可选的头,若发送带cookie的请求,则必须返回这个头,并设置为true,表示服务器允许客户端发送Cookie。   需要注意的是,这个头被设置为true时,Access-Control-Allow-Origin不允许使用*,而且只能指定单一域名,否则浏览器会报如下错误。

undefined

综上, 发送cookie的CORS的正确姿势是:

  • 客户端为fetch配置 credentials 项:credentials: 'include';
  • 服务端返回的responseHeader中携带如下两个头信息字段
    Access-Control-Allow-Origin: Origin;
    Access-Control-Allow-Credentials : true
    

The include part tells Chrome that you want to send a CORS request to the server which sends along the cookies properly

Chrome插件里怎么做

通过了解CORS原理,我们知道了不支持CORS的服务端会返回一个正常的HTTP回应,只是由于没有Aceess-control相关的响应头,导致浏览器对响应进行了拦截。利用chrome.webRequest API,对onHeadersReceived进行监听,即可在每次接收到 HTTP(S) 响应标头时,添加需要的header字段,让浏览器以为这些头信息是服务端返回的,从而绕过CORS限制。

//Breaking the CORS Limitation
chrome.webRequest.onHeadersReceived.addListener(details=>window.onHeadersReceivedCallback(details), {
  urls: ['<all_urls>']
}, ["blocking", "responseHeaders"]);

简单请求和复杂请求

只设置Access-Control-Allow-Origin和Access-Control-Allow-Crendentials两个字段,并不能满足所有的场景。例如,我们在使用Antd的Upload组件进行上传图片时,Chrome会报如下错误:

undefined

Preflight

Access-Control-Allow-Origin响应头字段可以允许跨域 AJAX, 但对于非简单请求,CORS 机制跨域会首先进行 preflight(一个 OPTIONS 请求,表示这个请求是用来询问的)

undefined

简单请求

具体是指请求方法是简单方法且请求头是简单头的 HTTP 请求。具体地,

  • 简单方法包括GET, HEAD, POST。
  • 简单头包括:Accept, Accept-Language, Content-Language,以及值为application/x-www-form-urlencoded, multipart/form-data, text/plain 其中之一的 Content-Type 头。 对于非简单请求浏览器会首先发送 OPTIONS 请求(成为 preflight)

Access-Control-Request-Headers

Access-Control-Request-Headers 是 preflight 请求中用来标识真正请求将会包含哪些头部字段, 例如下述请求中Access-Control-Request-Headers字段的值是x-requested-with,告知服务器,实际请求将携带这个自定义请求首部字段。服务器据此决定,该实际请求是否被允许,如果允许,服务器应当在对应的Access-Control-Allow-Headers响应头中包含这个字段。 否则即使返回 200 preflight 也会失败。

undefined

查看Antd Upload组件的源码,我们看到,在上传图片时,增加了x-requested-with请求头,由此触发了非简单请求,会在CORS时先进行Option询问

undefined

综上,为了处理非简单请求下的CORS,除了Access-Control-Allow-Origin和Access-Control-Allow-Crendentials外,我们还需要在Header劫持中添加如下配置

Access-Control-Allow-Methods:"*",
Access-Control-Allow-Headers:"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"