浏览器同源策略
同源策略是浏览器的一种安全策略。目前所有浏览器都施实行这个策略,这是由网景公司(NetScape)1995年引入的。
定义
同源是指的三个相同,简单来说就是:协议相同、域名相同、端口相同,如果这三者有任一不同,则会受到同源策略的限制,包括但不限于读取Cookie
。
举个栗子:这个网站http://www.godrry.com/archives/why-cant-you-admit-youre-wrong.html
- 协议是
http://
- 域名是
www.godrry.com
- 端口是
80
,当然80是默认端口,可以省略
它的同源情况可能有以下几种:
http://www.godrry.com/category/life/ => [同源]
http://my.godrry.com/category/life/ =>[域名不同]
https://www.godrry.com/category/life/ =>[协议不同]
http://www.godrry.com:6670/category/life/ =>[端口不同]
意义
浏览器的同源策略是一种保护,它必须要约束好不同源网站之间的联系,确保他们能够正常工作且互相不打扰。比如我们在a网站登录,登录信息存储在Cookie
中,此时我们又打开了B网站,那么如果不约束A和B之间的关系,那么B就有可能读取到A中的Cookie
,用户的信息安全就会受到威胁。
限制
如果非同源,则会有以下限制
- 无法获取
DOM
- 无法读取
Cookie
、LocalStorage
、IndexDB
- 无法发送
Ajax
请求
相对来说,这些限制保证了用户的信息安全,约束了网站之间的行为,但是有时候可能也会带来一些不便。所以我们有一些方法可以绕过同源策略的限制。接下来我们就来聊一聊如何不受同源策略的限制发送Ajax
请求。
跨域资源共享CORS
CORS:全称cross-origin resource sharing,属于W3C制定的标准,用于允许浏览器向跨源服务器发出XML请求,丰富了
Ajax
的应用场景。这其实是现代浏览器调整政策,放松了同源策略。
参与者
浏览器和服务器是CORS的主要参与者,浏览器要支持CORS才能够发出请求和接收响应,而跨源服务器则要实现CORS接口。目前所有的浏览器都支持CORS(IE不低于10),当浏览器发现此次请求属于跨源请求,则会自动添加一些附带的请求头,有时候还会进行一次预请求;跨源服务器则要通过配置才能够实现CORS接口。
在两者中,跨源服务器的配置属于关键。
CORS请求分为2种
CORS请求分为 简单请求 和 非简单请求,浏览器的处理方式并不相同。
简单请求
如果一次请求满足以下两大条件,那么这个请求就属于简单请求(如果不满足则属于非简单请求):
- 请求方法限于:
HEAD
,GET
,POST
- HTTP的头部信息限于:
Accept
,Accept-Language
,Content-Language
,Last-Event-ID
,Content-Type
- 而
Content-Type
限于:application/x-www-form-urlencoded
、multipart/form-data
、text/plain
Last-Event-ID
头信息,浏览器发送这个请求头用来帮助服务器端重建连接。
浏览器在发送CORS请求时,如果发现这是一个跨源简单请求,会在请求头中添加Origin
字段,告诉服务器这次请求来自于哪里,其值包括请求源的协议,域名和端口。服务器根据请求头中的Origin
来判断是否许可这次请求。
许可请求:
服务器会在响应头中添加
Access-Control-Allow-Origin: http://www.godrry.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Godrry
Access-Control-Allow-Origin
表示允许跨源请求的域名,一般会有2种值,一是发出请求的域名,一是*
代表允许所有域名跨源请求。【必选】Access-Control-Allow-Credentials
表示是否将响应内容暴露给请求源,响应内容包括cookies, authorization headers 或 TLS client certificates
, 设置为true则表示允许,不设置则为false,但不允许设置false,所以如果你需要它为true就设置,不需要则可以忽略该字段。【可选】
另外,CORS请求默认不发送Cookie和HTTP认证信息,所以如果要在XML请求时携带这些信息,则需要配置下属性
let xhr = new XMLHttpRequest()
xhr.withCredentials = true
如果要发送Cookie,那么服务器响应头中的Access-Control-Allow-Origin
就不能设置为*
,必须明确指定与请求源同源的域名,此时的Cookie信息依然受到同源策略的限制,只有服务器域名设置的Cookie才会随请求发送,另外document.cookie
是无法读取到这段Cookie信息
Access-Control-Expose-Headers
表示是否暴露其他响应头给请求源,默认包括Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
,这些都可以通过XML的getResponseHeader()
方法拿到,但是如果你需要暴露其他响应字段给请求源,那么就需要指定一下哪些响应头。像上面设置Access-Control-Expose-Headers: Godrry
,那么Godrry响应头就可以通过getResponseHeader('Godrry')
拿到它的值。【可选】
不许可请求:
服务器通过请求头中的Origin字段,判断不许可该请求,那么服务器也会给予响应,但是不会包含Access-Control-Allow-Origin
字段,当浏览器接收到这个响应并没有发现该响应头时抛出错误,XMLHttpRequest
的onerror事件捕获错误。注意此时服务器返回的Status Code
有可能是200,所以无法据此判断是否请求成功。
非简单请求
如果浏览器发现本次请求不是简单请求,那么非简单请求会包含两个请求:预检请求和正式请求。当然如果预检请求不通过,则不会进行真正的请求。
预检请求(preflight)
浏览器会在正式请求前发送一个预检请求,用来咨询服务器此次请求是否被许可,许可内容包括Origin、请求方式(动词)和请求头。
接下来我们发送一个非简单请求,向服务器example.godrry.com
,请求动词为PUT
,并且设置一个自定义请求头:
let xhr = new XMLHttpRequest()
xhr.open('PUT','http://example.godrry.com')
xhr.setRequestHeader('My-Custom-Header', 'bingo')
xhr.send()
浏览器发送预检请求(请求动词为OPTION
),设置以下请求头告知服务器正式请求的请求动词和额外的头信息字段等,
Access-Control-Request-Headers: content-type,my-custom-header
Access-Control-Request-Method: PUT
Origin: http://demo.godrry.com
...
如果服务器许可此次请求,则会有以下响应头信息:
access-control-allow-headers: content-type,my-custom-header
access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE
access-control-allow-origin: *
access-control-allow-origin
表示服务器许可谁进行CORS,也可以设置为*
access-control-allow-methods
表示服务器许可CORS的请求方式access-control-allow-headers
表示服务器许可CORS的所有头信息
接下来浏览器就会发送出正式请求,正式请求就像简单请求一样,在请求头信息中添加Origin
,服务器的响应头信息中也需要一直携带Access-Control-Allow-Origin
。如果再次请求还会进行预检,如果希望预检通过后再次请求时省略预检这一步,可以由服务器设置Access-Control-Max-Age
字段,设置预检请求有效期。
如果服务器不许可此次请求,那响应头信息中没有相关CORS字段,则浏览器会抛出一个错误,由XML的onerror
事件捕获。浏览器控制台也会打印出相关错误。
Comments | NOTHING