浏览器同源策略与跨域请求

同源策略

同源策略即 same-origin policy是浏览器为了数据安全而产生的一个概念,最初,同源策略是指 cookie,因为同源策略的存在,B网站不能访问A网站的cookie,从而保护了用户的隐私,首先解释下什么是同源,所谓同源强调的是三个相同:
1. 协议相同
2. 域名相同
3. 端口相同

举个简单的例子来说https://xiaoysec.github.io/test.html 这个网址中,http是协议名,xiaoysec.github.io是域名,80是端口名(默认是80端口)

同源策略对访问的限制

1.Cookie、LocalStorage 和 IndexDB 无法读取。
2.DOM 无法获得
3.Ajax无法请求

如何绕开同源策略

1.针对于cookie来说,如果两个网站的一级域名是相同的,只是二级域名不同的话,可以通过设置document.domain来实现cookie的共享

document.domain = 'example.com';

现在,A网页通过脚本设置一个 Cookie。
document.cookie = "test1=hello";

B网页就可以读到这个 Cookie。
var allCookie = document.cookie;

或者在服务器端程序中设置cookie的一级域名

对于LocalStorage 和 IndexDB 以及iframe的同源策略避开方法可以参看参考资料,因为不是很常见所以不在这边进行过多的介绍

2.Ajax避开同源策略限制的方法

Ajax的解决方案比较重要,在工作中经常会遇到

  • JSONP
  • WebSocket
  • CORS

JSONP

JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
要理解JSONP的基本原理,首先要说的是在img标签的src中引用外部的资源是不存在同源策略限制的,那么就可以利用这点来完成Ajax请求,JSONP的基本思想就是插入一个script标签,请求数据的同时一般会给定一个回调函数名,而这个回调函数是在当前的文件中定义好的,API返回数据的形式通常是 callBack(‘data’);返回后页面就会执行callBack函数了,通俗的说JSONP返回的是一段js代码

假设JSONP请求如下:

jsonp({ url: 'http://path/to/server/b', params: {A: a, B: b},
    success: function myCallback (response) {}
})

背后其实在进行:

  • 拼接一个script标签
    <script src="http://path/to/server/b?A=a&B=b&callbackFunctionName=myCallback" ></script>
    从而触发对指定地址的GET请求

  • 那服务器端对这个GET请求进行处理,并返回字符串 “myCallback(‘response value’)”

  • 那前端script加载完之后,其实就是在script中执行myCallback(‘response value’)

CORS 跨域资源共享

CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

当浏览器发现请求是跨域的,就会在请求头中添加一些字段,或者是会多一次预请求,对于浏览器用户来说是没有感知的,浏览器自动来实现了,实现CORS的关键在于服务器程序的支持

CORS请求分为简单请求和非简单请求,简单请求需要同时满足两个条件
1. 请求方法为HEAD、GET、POST
2. HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

简单请求

对于简单CORS请求,浏览器自动在请求头中添加上了Origin字段,表示这个跨域请求的来源,服务器端可以根据Origin判断是否允许跨域请求,如果允许跨域请求,那么服务器给出的响应会包含Access-Control-Allow-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

(1)Access-Control-Allow-Origin

该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

(2)Access-Control-Allow-Credentials

该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。

(3)Access-Control-Expose-Headers

该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)可以返回FooBar字段的值。

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

下面是一段浏览器的JavaScript脚本。

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。
浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。下面是这个”预检”请求的HTTP头信息。

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字段,”预检”请求的头信息包括两个特殊字段。

(1)Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。

(2)Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是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

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
Access-Control-Allow-Origin: *

如果浏览器否定了”预检”请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。控制台会打印出如下的报错信息。

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

服务器回应的其他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

(1)Access-Control-Allow-Methods

该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。

(2)Access-Control-Allow-Headers

如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。

(3)Access-Control-Allow-Credentials

该字段与简单请求时的含义相同。

(4)Access-Control-Max-Age

该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

浏览器的正常请求和回应

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

下面是”预检”请求之后,浏览器的正常CORS请求。

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

上面头信息的Origin字段是浏览器自动添加的。

下面是服务器正常的回应。

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8

上面头信息中,Access-Control-Allow-Origin字段是每次回应都必定包含的。

参考文档

http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html
http://www.ruanyifeng.com/blog/2016/04/cors.html
https://segmentfault.com/q/1010000009708151

相关文章
相关标签/搜索