全网最全的跨域资源共享CORS方案分析

1. 同源策略

浏览器最基本的安全规范------同源策略(Same-Origin Policy)。

所谓同源是指域名,协议,端口相同,不同源的浏览器脚本(javascript、ActionScript、canvas)在没明确授权的情况下,不能读写对方的资源

同源策略规定了浏览器脚本操作web数据的基本原则。

若没有这一基本原则,那么:

① 某域下DOM元素被另一方任意操作、篡改, 导致页面显示失控

② 某域下的Cookie等与该域相关的密切数据可以任意读取,导致与该域密切相关的浏览器cookie片段可能失真

③ 恶意网站能随意执行Ajax脚本偷取隐私数据,导致该域下核心业务数据被抓取。

2. 同源策略在实施中面临的问题

默认的同源策略 限制了脚本互操作其他域的能力,大棒一挥, 关闭了A站脚本正常访问B站数据的需求

所以有以下变通方法:

① 实现CORS (Cross-Origin Resource Sharing) :

② 使用JSONP (JSON Padding)

③ 建立一个本地代理服务器,这样先同源访问,由代理服务器转发请求

以上①CORS是w3C 对于跨域请求推出的明确方案; ②③方式都是一种Hack行为。

3. CORS跨域请求方案

w3c推出的跨域请求方案: 让web服务器明确授权非同源页面脚本来访问自己, 以Response 特定标头 Access-Control*-****-***** 体现; 目前现代浏览器均认可并支持这些标头。

CORS新HTTP标头,为浏览器提供了获得授权,脚本才能访问其他域名页面数据的标准姿势

另外,即使跨域请求授权失败,内容其实已经返回了,只不过浏览器在解析了内容之后,发现这是不允许的,所以帮你拦截了,这是浏览器遵守CORS规范的行为。

3.1 常规的携带Cookie Ajax跨域Get请求

ini 复制代码
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/credentialed-content/';
    
function callOtherDomain(){
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true;   // 指示ajax请求时是否携带cookies,authorization,TLS客户端证书等凭据。
    invocation.onreadystatechange = handler;
    invocation.send(); 
  }
}

CORS规范

① 浏览器遇到跨域请求,浏览器会自动携带Origin标头(指示请求来自于哪个站点),判断需要预检preflight后,还会自动给你发起Option请求。

Web服务器实现跨站访问授权逻辑, 授权结果在Response中以 Access-Control--******* 标头体现

最常见的Access-Control-Allow-Origin标头包含 * / Origin /null 三种响应值;

当请求是携带凭据的跨域请求,不可囫囵吞枣地将Access-Control-Allow-Origin 指定为*通配符,而必须指定特定Origin . developer.mozilla.org/en-US/docs/...

③ 浏览器会遵守Access-Control--*******-- 标头值所施加的跨域限制

下面产生了一个ajax 跨域请求,请求携带了cookie凭据。

yaml 复制代码
GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example    // 浏览器会自动携带
Cookie: pageAccess=2


HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true       // Access-Control-Allow-Credentials: true 指令浏览器,服务器允许带凭据的请求。
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain


[text/plain payload]

3.2 预检Preflight

对于非简单Ajax请求(通常是GET以外的HTTP方法,或者某些MIME类型的POST用法),CORS规范要求发起 "预检" 请求。

"预检请求"的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

不过,这个请求不需要你手动发起,浏览器会自动发起OPTIONS请求,获得web服务器要判断的跨域方法和请求头,

然后,在服务器"批准"时,使用实际的HTTP请求方法发送实际请求。

下图显示 浏览器判断 非简单请求的逻辑图:

下面使用POST动作发起Ajax跨域请求,同时自定义了一个request header: X-PINGOTHER, 该请求触发浏览器预检行为

ini 复制代码
const invocation = new XMLHttpRequest();
const url = 'http://bar.other/resources/post-here/';
const body = '<?xml version="1.0"?><person><name>Arun</name></person>';
    
function callOtherDomain(){
  if(invocation)
    {
      invocation.open('POST', url, true);
      invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
      invocation.setRequestHeader('Content-Type', 'application/xml');
      invocation.onreadystatechange = handler;
      invocation.send(body); 
    }
}

3.3 程序员调试CORS的苦恼

跨域请求发生在A--->B 两站,作为某一方开发人员,调试CORS相对麻烦。

curl可优雅验证/调试服务端CORS方案: 核心的是添加Origin请求头(并不能模拟客户端跨域ajax请求)。

example.com 向谷歌站点发起一个跨域Get请求

curl -H "Origin: http://example.com" https://www.googleapis.com/discovery/v1/apis?fields=11 --verbose

从浏览器Network,将请求格式 以cUrl形式拷贝出来,改改。

为避免 shell 解析误解, curl请求最好对 url 加上单引号。

总结

① 浏览器同源策略 作用对象是 浏览器脚本;

② 存在跨域请求的场景,某些方案是Hack行为;

③ w3c推出的CORS是标准的跨域请求方案,思路是在服务端Response标头体授权, 浏览器遵守该授权标头。

④ 对于非简单的脚本跨域请求,浏览器会自动发起 Option请求预检, 大部分时候无需关注

⑤ curl工具帮助高效、优雅调试CORS方案。

后面会介绍浏览器的另一个有用的安全策略: Cookie SameSite策略。

相关推荐
卡拉叽里呱啦19 分钟前
缓存-变更事件捕捉、更新策略、本地缓存和热key问题
分布式·后端·缓存
David爱编程23 分钟前
线程调度策略详解:时间片轮转 vs 优先级机制,面试常考!
java·后端
码事漫谈1 小时前
C++继承中的虚函数机制:从单继承到多继承的深度解析
后端
阿冲Runner1 小时前
创建一个生产可用的线程池
java·后端
写bug写bug1 小时前
你真的会用枚举吗
java·后端·设计模式
喵手2 小时前
如何利用Java的Stream API提高代码的简洁度和效率?
java·后端·java ee
m0_480502642 小时前
Rust 入门 生命周期-next2 (十九)
开发语言·后端·rust
张醒言2 小时前
Protocol Buffers 中 optional 关键字的发展史
后端·rpc·protobuf
鹿鹿的布丁3 小时前
通过Lua脚本多个网关循环外呼
后端