同源策略
- 它是浏览器最核心也是最基本的安全功能,浏览器的同源策略,限制了来自不同的 “document” 或脚本对当前的 “document” 的读取或设置某些属性
- 如果没有同源策略,可能 a.com 的一段 Javascript 脚本在 b.com 未曾加载此脚本时,也可以随意涂改 b.com 的页面,为了不发生页面行为混乱,浏览器提出 ”Origin” 这一概念,来自不同 Origin 的对象互不干扰。
- 影响 Origin(源) 的因素有:host(域名或IP)、子域名、端口、协议:
- 在浏览器中 <script><img><iframe><link> 等标签都可以跨域加载资源,而不受到同源策略的限制。这些带 “src” 属性的标签每次加载时,实际上是由浏览器发起了一次GET请求。不同于 XMLHttpRequest 的是,通过 src 属性加载的资源,浏览器限制了 JavaScript 的权限,使其不能读、写返回内容。
- XMLHttpRequest 可以访问同源资源,一开始不能跨域访问,但随着互联网发展,W3C制定了 XMLHttpRequest 跨域访问的标准(CORS),它需要通过目标与返回的HTTP头来授权是否可以跨域访问,因为HTTP头对于 JavaScript 来说一般无法控制
- 但是浏览器的同源策略并非坚不可摧,如 IE8 的 Css 跨域漏洞:
www.a.com/test.html:
<body>
{}body{font-family:
aaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbb
</body>
www.b.com/test2.html:
<style>
@import url("http://www.a.com/test.html")
</style>
<script>
setTimeout(function(){
var t = document.body.currentStyle.fontFamily;
alert(t);
},2000);
</script>
- 上述代码中 www.b.com/test2.html 中通过 @import 加载了 www.a.com/test.html 为 Css 渲染进入当前页面 DOM,同时通过 document.body.currentStyle.fontFamily 访问此内容,问题在 IE 的 CSS Parse 过程中,IE 将 fontFamily 后面的内容当做 value,而通过 <script> 等标签仅能加载资源,不能读写资源内容,而这个漏洞能够跨域读取页面内容,绕过了同源策略
CORS
- CORS 是 W3C 制定的“跨域资源共享”的标准,它允许浏览器向跨源服务器发出 XMLHttpRequest 请求,克服了 AJAX 只能同源的使用限制
- CORS 需要浏览器和服务器同时支持,目前所有浏览器都支持该功能,IE 浏览器不能低于 IE10,整个通信过程是浏览器自动完成的,不需要用户参与,CORS 与 AJAX 代码上没有差别,浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息
- 因此实现 CORS 的通信关键是服务器,只要服务器实现了 CORS 接口,就可以跨源通信
两种请求
- 浏览器将 CORS 请求分为两类:简单请求(Simple Request)和非简单请求(not-so-simple reuqest)
- 只要满足以下两种条件就属于简单请求,否则属于非简单请求
请求方法是以下三种方法之一:
HEAD、GET、POST
HTTP的头信息不超出以下几种字段:
Accept、Accept-Language、Content-Language、Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求
- 对于简单请求,浏览器直接发出 CORS 请求,就是在头信息中添加一个 Origin 字段
- 下面是浏览器在一个请求中添加 Origin 字段的例子
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
- 上面头信息中,Origin 字段用来说明本次请求来自哪个源(协议+域名+端口),服务器根据这个值,决定是否同意本次请求
- 如果 Origin 指定的源不在许可范围内,服务器会返回一个正常的 HTTP 协议,但头信息没有包含 Access-Control-Allow-Origin 字段,从而抛出一个错误,被 XMLHttpRequest的onerror 回调函数捕获。注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是200
- 如果 Origin 指定的域名在许可范围内,服务器的响应会多出几个头信息
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
-
Access-Control-Allow-Origin:该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个*,表示接受任意域名的请求
-
Access-Control-Allow-Credentials:该字段可选,它是一个布尔值,表示是否允许发送 Cookie
-
Access-Control-Expose-Headers:该字段可选,CORS 请求时,XMLHttpRequest 对象的 getResponseHeader() 方法只能拿到 6 个基本字段:ache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果想拿到其它字段就必须在 Access-Control-Expose-Headers 里面指定,上面例子指定了 getResponseHeader('FooBar') 可以返回 FooBar 字段的值
withCredentials 属性
- CORS 请求默认不发送 Cookie 和 HTTP 认证信息,如果要把 Cookie 发送到服务器,一方面要服务器同意
Access-Control-Allow-Credentials = true
- 另一方面开发者必须在 AJAX 请求中打开 withCredentials 属性
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
- 有些浏览器会默认一起发送 Cookie,这时可以显式关闭 withCredentials
xhr.withCredentials = false;
- 如果要发送 Cookie,Access-Control-Allow-Origin 就不能为星号,必须指定明确的、与请求网页一致的域名,并且 Cookie 依然遵循同源策略
非简单请求
- 非简单请求是那种对服务器有特殊需求的请求,比如 PUT 或 DELETE 方法,或者 Content-Type 字段的类型是 application/json
- 非简单请求的 CORS 请求会在正式通信之前增加一次 HTTP 请求查询,称为“预检”请求
-
浏览器会先询问服务器,当前网页是否在服务器的许可名单中,以及可以使用那些 HTTP 动词和头字段信息,只有得到肯定大幅,浏览器才会发送 XMLHttpRequest 请求,否则报错
- 下面是一段浏览器的 JS 脚本
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
- 上面代码 HTTP 请求方法是 PUT,并且发送一个自定义的头信息 X-Custom-Header
- 浏览器发现,这是一个非简单请求,就自动发出一个“预检”请求,要求服务器确认,下面是一个“预检”请求的例子
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
- “预检”请求的方法是 OPTIONS,表示这个请求是用来询问的,头信息里关键字段是 Origin,表示来自哪个源
- 除了 Origin 字段,还包括两个特殊字段
-
Access-Control-Request-Method:必须,用来列出 CORS 请求用的 HTTP 方法,上例是 PUT
-
Access-Control-Request-Headers:该字段是一个逗号分离的字符串,指定额外发送的头字段信息,上例是 X-Custom-Header
预检请求的回应
- 服务器收到“预检”请求后检查 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 字段后确认允许跨源请求,就可以作出回应
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
Access-Control-Allow-Origin: *
- 如果浏览器否定了“预检”请求会返回一个正常的 HTTP 回应,但不包含 CORS 相关头信息,因此会触发一个错误,被 XMLHttpRequest 对象的 onerror 回调函数捕获
- 服务器回应的其它 CORS 相关字段如下:
Access-Control-Allow-Methods: GET, POST, PUT #表示支持的跨域请求方法
Access-Control-Allow-Headers: X-Custom-Header #表示服务器支持的所有头信息字段
Access-Control-Allow-Credentials: true #该字段与简单请求时相同
Access-Control-Max-Age: 1728000 #指定本次预检请求的有效期,单位为秒,即在缓存该条回应期间,不用在发出另一条预检请求
网友评论