同源策略和跨域
字数统计:3.1k字目录
转载:
ajax跨域,这应该是最全的解决方案了
参考:
浏览器同源政策及其规避方法
跨域资源共享 CORS 详解
正确面对跨域,别慌
前端常见跨域解决方案(全)
同源策略
概念
同源即指:协议相同、域名相同、端口号相同。
举例来说,http://www.example.com/dir/page.html
这个网址,协议是http://
,域名是www.example.com
,端口是80
(默认端口可以省略)。它的同源情况如下。1
2
3
4http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
目的
同源政策的目的,是为了为了保证使用者信息的安全,防止恶意网站篡改用户数据。
设想这样一种情况:A网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取A网站的Cookie
,会发生什么?
很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie
往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
由此可见,”同源政策”是必需的,否则 Cookie
可以共享,互联网就毫无安全可言了。
限制范围
随着互联网的发展,”同源政策”越来越严格。目前,非同源的网站之间,共有四种行为受到限制。1
2
3
4(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。
(4)无法通过 flash 发送 http 请求
跨域
同源策略做了很严格的限制,但是在实际的场景中,又确实有很多地方需要突破同源策略的限制,也就是我们常说的跨域。
Cookie
同源策略最早被提出的时候,为的就是防止不同域名的网页之间共享 cookie
。Cookie
是服务器写入浏览器的一小段信息,只有同源的网页才能共享。但是,两个网页一级域名相同,只是二级域名不同,浏览器允许通过设置document.domain
共享Cookie
。
举个例子,https://market.douban.com
和https://book.douban.com
,这两个网页的一级域名都是 douban.com
,如果我在 market.douban.com
中执行了1
2
3
4
5
6
7
8#A、B页面设置相同的document.domain
document.domain = 'douban.com'
# A页面通过脚本设置一个Cookie
document.cookie = 'cross=yes'
或
document.cookie = 'cross=yes;path=/;domain=douban.com'
# B页面就可以读取到Cookie
var allCookie = document.cookie
这样设置了 cookie
之后,在book.douban.com
中是可以取到这个cookie
的。
除了在前端设置之外,也可以直接在response
里将cookie
的domain
设置成 .douban.com
。
Ajax
什么是Ajax跨域
ajax跨域的原理
ajax
出现请求跨域错误问题,主要原因就是因为浏览器的“同源策略”,可以参考
ajax跨域的表现
ajax
请求时,如果存在跨域现象,并且没有进行解决,会有如下表现:(注意,是ajax
请求,请不要说为什么http
请求可以,而ajax
不行,因为ajax
是伴随着跨域的,所以仅仅是http
请求ok
是不行的)
第一种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource
,并且The response had HTTP status code 404
出现这种情况的原因如下:
本次ajax请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS
)
服务器端后台接口没有允许OPTIONS
请求,导致无法找到对应接口地址
解决方案: 后端允许options请求
第二种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource
,并且The response had HTTP status code 405
这种现象和第一种有区别,这种情况下,后台方法允许OPTIONS
请求,但是一些配置文件中(如安全配置),阻止了OPTIONS
请求,才会导致这个现象
解决方案: 后端关闭对应的安全配置
第三种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource
,并且status 200
这种现象和第一种和第二种有区别,这种情况下,服务器端后台允许OPTIONS
请求,并且接口也允许OPTIONS
请求,但是头部匹配时出现不匹配现象
比如origin
头部检查不匹配,比如少了一些头部的支持(如常见的X-Requested-With
头部),然后服务端就会将response
返回给前端,前端检测到这个后就触发XHR.onerror
,导致前端控制台报错
解决方案: 后端增加对应的头部支持
第四种现象:heade contains multiple values '*,*'
表现现象是,后台响应的http头部信息有两个Access-Control-Allow-Origin:*
说实话,这种问题出现的主要原因就是进行跨域配置的人不了解原理,导致了重复配置,如:
常见于.net
后台(一般在web.config
中配置了一次origin
,然后代码中又手动添加了一次origin
(比如代码手动设置了返回*
))
常见于.net
后台(在IIS
和项目的webconfig
中同时设置Origin:*
)
解决方案(一一对应):
建议删除代码中手动添加的*
,只用项目配置中的即可
建议删除IIS下的配置*
,只用项目配置中的即可
如何解决ajax跨域
一般ajax跨域解决就是通过JSONP
解决或者CORS
解决,如以下:(注意,现在已经几乎不会再使用JSONP
了,所以JSONP
了解下即可)
JSONP
jsonp 其实算是一种 hack 形式的请求。
jsonp 的本质其实是请求一段 js 代码,是对静态文件资源的请求
,所以并不遵循同源策略
。但是因为是对静态文件资源的请求,所以只能支持 GET 请求,对于其他方法没有办法支持。
设置CORS
CORS
是一个W3C
标准,全称是”跨域资源共享”(Cross-origin resource sharing
)。
它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX
只能同源使用的限制。
本文详细介绍CORS的内部机制。
CORS简介
CORS
需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10
。
整个CORS
通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX
请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
CORS两种请求
浏览器将CORS请求分成两类:简单请求(simple request
)和非简单请求(not-so-simple request
)。
只要同时满足以下两大条件,就属于简单请求。1
2
3
4
5
6
7
8
9
10
11
12
13(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简单请求
对于简单请求,浏览器直接发出CORS
请求。具体来说,就是在头信息之中,增加一个Origin(protocal + host + path + port)
字段,来标明这个请求是来自哪里。
下面是一个例子,浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin
字段。1
2
3
4
5
6
7GET /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
指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
1 | Access-Control-Allow-Origin: http://api.bob.com |
上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头。
(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字段的值。
withCredentials 属性
上面说到,CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials
字段。Access-Control-Allow-Credentials: true
另一方面,开发者必须在AJAX请求中打开withCredentials
属性。1
2var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
否则,即使服务器同意发送Cookie
,浏览器也不会发送。或者,服务器要求设置Cookie
,浏览器也不会处理。
但是,如果省略withCredentials
设置,有的浏览器还是会一起发送Cookie
。这时,可以显式关闭withCredentials
。1
xhr.withCredentials = false;
需要注意的是,如果要发送Cookie
,Access-Control-Allow-Origin
就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie
依然遵循同源政策,只有用服务器域名设置的Cookie
才会上传,其他域名的Cookie
并不会上传,且(跨源)原网页代码中的document.cookie
也无法读取服务器域名下的Cookie
。
CORS非简单请求
与简单请求
最大的不同在于,非简单请求
实际上是发送了两个请求。
预请求
首先,在正式请求之前,会先发送一个预请求(preflight-request),这个请求的作用是尽可能少的携带信息,供服务端判断是否响应该请求。
浏览器
浏览器发送预请求,请求的 Request Method 会设置为 options。
另外,还会带上这几个字段:1
2
3Origin: 同简单请求的origin
Access-Control-Request-Method: 请求将要使用的方法
Access-Control-Request-Headers: 浏览器会额外发送哪些头信息
服务端
服务端收到预请求之后会根据request中的origin,Access-Control-Request-Method和Access-Control-Request-Headers判断是否响应该请求。
如果判断响应这个请求,返回的response中将会携带:1
2
3Access-Control-Allow-Origin: origin
Access-Control-Allow-Methods: like request
Access-Control-Allow-Headers: like request
如果否定这个请求,直接返回不带这三个字段的response就可以,浏览器将会把这种返回判断为失败的返回,触发onerror方法
正式响应
如果预请求被正确响应,接下来就会发送正式请求,正式请求的request和正常的 ajax 请求基本没有区别,只是会携带 origin 字段;response和简单请求一样,会携带上Access-Control-*这些字段
WebScoket
websocket 不遵循同源策略。
但是在 websocket 请求头中会带上 origin 这个字段,服务端可以通过这个字段来判断是否需要响应,在浏览器端并没有做任何限制。