CORS - 跨域请求拦截
统一入口 聚而为一

跨域很好判断,处理方式看浏览器console输出操作,一般都是在被嵌入页面链接或被调用的接口服务端代理上添加放行操作。

(另外一个处理方案是同域化处理,即将被嵌入一方代理到referer域名的网站以内,此方案的前置条件很多:比如需要被嵌入方、referer域名一方没有过多限制,这里也是比较考验对代理软件的综合实现的理解与处理经验的,如果可实现的话,这个是更好的方式)。

请求预检 preflight requests

现代保持更新的浏览器常用的请求有POST GET PUT DELETE等,不知道大家有没有关注过还有个请求类型叫OPTIONS。一般来说preflight预检请求,指的就是OPTIONS请求。它会在浏览器认为即将要执行的请求可能会对服务器造成不可预知的影响时,由浏览器自动发出。通过预检请求,浏览器能够知道当前的服务器是否允许执行即将要进行的请求,只有获得了允许,浏览器才会真正执行接下来的请求。 通常preflight请求不需要用户自己去管理和干预,它的发出的响应都是由浏览器和服务器自动管理的。

本文以服务端为nginx server 代理为例进行参数说明。

这里面主要关心origin Access-Control-Request-Method Access-Control-Request-Headers这三个字段,依次代表访问来源、真实请求的方法和真实请求的请求头。

相对应的,响应里我们需要关心的是Access-Control-Allow-Origin Access-Control-Allow-Headers Access-Control-Allow-Methods 这三个字段,依次代表当前请求支持的访问域、支持的自定义请求头、支持的请求方法,如果即将执行的请求的任意一项不在支持范围内,浏览器就会自动放弃执行真实请求。同时抛出CORS错误。最后一项Access-Control-Max-Age代表该预检请求的有效期,在有效期内浏览器不会再为同一请求执行预检操作。 那具体什么情况下,会触发preflight请求呢?请看下一节。

何时触发PreFlight请求

preflight预检请求属于CORS规范的一部分,目前所有的现代浏览器都实现了此规范,但是部分浏览器对规范内容有扩充。MDN上指出,一共有五项必须条件需要满足,否则浏览器在执行真实请求之前会发出预检请求,以免在获得允许之前对服务器产生不可预知的影响。 以下五项条件只要有任意一项不满足即会发送预检请求:

只能够使用GET POST HEAD

只能包含以下九种请求头 Accept Accept-Language Content-Language Content-Type DPR Downlink Save-Data Viewport-Width Width

Content-Type只能包含以下三种类型 text/plain multipart/form-data application/x-www-form-urlencoded

XMLHttpRequestUpload对象没有注册任何事件监听器

请求中不能使用ReadableStream对象

对于常规的开发来说,主要的限制在前三条。最常见的场景是设置了自定义请求头和Content-Type类型不在支持的范围以内。

为什么会有PreFlight请求

我们现在大概明白了preflight请求是什么和什么场景触发preflight请求。 那么设计preflight请求的目的是什么呢?它能够从哪些路径帮我们规避问题呢?谈到这里,其实就谈到CORS跨域资源共享了。因为preflight预检请求就是为CORS服务的,是CORS规范中的一部分。通过限制跨域访问,可以极大的提高网页的安全性。同时对于不支持CORS的旧服务器,通过preflight请求确认对CORS的支持情况,来决定下一步的访问是否要继续,以免对服务器的数据产生不可预知的影响。 如果没有CORS,我们可以认为在没有特别指定和配置的情况下,所有网站的资源都是共享的,A网站可以通过代码访问到B网站的Cookie等隐私信息,反过来同样的B网站可以通过代码访问到A网站。而有了CORS这些访问默认都是不允许的,需要经过特别的配置才能够支持跨域访问。这就让那些对安全性有要求的网站,有了比较通用的途径去提高网站的安全性,同时又保证了一定的便利性。 具体的CORS的安全机制是比较复杂,这里不再详述,感兴趣的同学可以参考MDN的文档

如何正确的支持PreFlight请求

对于服务端开发来说,如果自己的请求可能会遇到有preflight请求的情况,我们需要怎么配置来支持preflight请求呢? 通常来说,我们需要关注的还是最关键的三个字段,Access-Control-Allow-Origin Access-Control-Allow-Headers Access-Control-Allow-Methods

 

 

浏览器报错分析

浏览器打开,按快捷键F12进入开发者模式分析报错。

Access to XMLHttpRequest at 'https://www.haudi.top/pages/rest/Agency/getBBxAgencyOperationByCondition' from origin 'http://pc.qq.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

https://www.haudi.top/pages/rest/Agency/getBBxAgencyOperationByCondition是内嵌接口调用,http://pc.qq.com是外部页面。

此时必须指定具体的'Access-Control-Allow-Origin' 响应头,此以报错为例,需要指定:

问题2 为chrome浏览器console输出拦截报错:

Access to XMLHttpRequest at 'https://www.haudi.top/pages/rest/Agency/getBBxAgencyOperationByCondition' from origin 'http://pc.qq.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

在问题一的基础上进一步要求添加Access-Control-Allow-Credentials响应头,此以报错为例,需要指定:

 

实践

实际跨站点调用时,往往还需要考虑不影响原有的站点内部潜在调用。前面的Access-Control-Allow-Origin限制就会影响www.haudi.top域名下的自调用。如何保持平滑的功能实现,可参考以下,在内嵌的接口方服务端(以nginx为例)添加如下判断逻辑,由于nginx不支持and条件,但可通过如下逻辑实现匹配referer来源域名为pc.qq.com,且请求方法为GET|POST|OPTIONS的特定请求,为其指定特定的响应头,同时不影响原有的响应头:

结合此逻辑,可以进一步设置map 逻辑来实现域名自动匹配,以实现更多的兼容性(有实际案例后补)

其中如下的参数已合并写入到上面的示例中。

如果请求预检不返回200状态时,它将会在chrome浏览器网络标签的请求资源的响应状态中显示50x,此时实现不了预检效果。

 

其它相关参数

 

 

 

参考

https://developer.mozilla.org/zh-CN/docs/Glossary/Preflight_request

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Headers

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Origin

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Request-Headers