什么是同源:
所谓同源是指:域名、协议、端口相同
域名:是否是一个域名下的资源
协议:是否都属于http协议或者https协议
端口:是否都无80 端口,或者其他端口
什么是浏览器的同源策略:
同源策略是一种约定,它是浏览器最核心也是最基础的安全功能。它的核心在与它认为来自
来自任何站点装载的信赖内容是不安全的,当被浏览器半信半疑的脚本运行在沙箱时,它们
只被允许访问来自同一个站点的资源,而不是来自其他站点可能怀有恶意的资源。
沙盒(sandbox),另称沙箱,是一种按照安全策略限制程序行为的执行环境。“沙盒”技术的实践运用流程是:让疑似病毒文件的可疑行为在虚拟的“沙盒”里充分运行,“沙盒”会记下它的每一个动作;当疑似病毒充分暴露了其病毒属性后,“沙盒”就会执行“回滚”机制:将病毒的痕迹和动作抹去,恢复系统到正常状态。
为什么要有跨域限制:
XMLHttpRequest同源策略
例如:AJAX同源策略主要是为了防止CSRF攻击,如果没有AJAX同源策略,
HTTP请求都会带上请求地址对应的cookie。
攻击流程:
用户登录自己的银行页面myback.com,myback.com向用户的cookie中添加用户标识。
用户浏览恶意页面evil.com,执行页面中ajax请求代码
evil.com向myback.com发起ajax http请求,请求会默认把mybank.com对应的cookie也同时发送
银行页面从发送cookie中提取用户标识,验证无误,response返回数据到evil.com中,导致数据泄露
由于ajax在后台执行,用户无法感知
DOM同源策略:不同域之间的iframe不能相互访问
假如没有了DOM同源策略,不同域之间的iframe可以互相访问
攻击流程:1、做一个假网站,用iframe嵌套一个银行网站,mybank.com
2、把iframe调整为和正真的银行网站没有区别
3、如果用户在mybank.com中输入账号和密码,我们主网站可以跨域访问到mybank.com的dom节点,
从而盗取账号密码
但是有时候我们需要突破域限制,去访问其他域下的资源
1、电商网站想通过用户浏览器加载第三方快递网站的物流信息
2、子站域名希望调用主站域名的用户资料接口,显示数据
3、.....
如何解决跨域问题:
CORS,全称跨域资源共享(Cross-Origin-Resourse-sharing),通过在HTTP请求中添加字段
来告诉浏览器,哪些不同来源的服务器是可以访问本站资源,哪些不能访问。
它允许浏览器向跨源服务器发出XHR请求,从而克服AJAX只能同源使用的限制。
CORS需要浏览器和服务器同时支持。
浏览器将CORS分为两类:简单请求和非简单请求
区别:简单请求需要同时满足一下两大条件
请求方法为:GET、HEAD、POST之一
请求头信息不超过以下几个字段:Accept:
Accept-Language:
Content-Language:
Last-Event-ID:
Content-Type:只限于三个值,application/x-www-form-urlencoded
、
multipart/form-data
、text/plain
其他全部为非简单请求
简单请求:
基本流程:
浏览器直接发出CORS请求,具体来说就是在头信息中增加一个Origin字段。
eg:如果浏览器发现这次属于跨源AJAX请求是简单请求,就自动在头信息中加一个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字段,就知道出错了,会抛出一个错误,被XHR的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
上面的字段中,有三个是和CORS请求相关字段,都以Access-Control开头
1、Access-Control-Allow-Origin:
该字段必须,他的值要么是Origin字段是值,
要么为 "*",表示接受任意域名请求
2、Access-Control-Allow-Credentials:
该字段可选。值为布尔值,表示允许发送cookie。
默认情况下,cookie不包括在CORS请求之中。设为true之后,即表示
服务器许可,cookie可以包含在请求中,一起发送给服务器,
如果不需要包含cookie,删除该字段即可
3、Access-Control-Expose-Headers:
该字段可选。CORS请求时,XHR对象的getRespouseHeader()方法只能拿到6个
基本字段:Cache-Control、Content-Language、Content-Type
Expires、Last-Modified、Pragma
如果想拿到其他字段,需要在这里说明,例子中可以返回foobar字段的值。
withCredentials属性:
上面说到,CORS请求默认不发送cookie和http认证信息,
如果要把cookie发送到服务器,一方面需要服务器同意
即Access-Control-Allow-Credentials 需要为true
另一方面,开发者需要在ajax请求中打开withCredentials
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
否则,即使服务器接收,浏览器也不会发送请求
如果省略withCredentials这个设置,有的浏览器还是会一起发送Cookie,这时,可以显示的关闭
xhr.withCredentials = false;
需要注意的是:如果要发送Cookie,Access-Control-Allow-Origin就不能设置为" * "号,
必须指定明确的、与请求网页一致的域名。同时,cookie依然遵循同源策略,
只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页
代码中的document.cookie也无法读取服务器域名下的Cookie
非简单请求:
"预检"请求
非简单请求是指对那种服务器有特殊要求的请求,
比如请求方法PUT,DELETE或者Content-Type字段的类型为application/json
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
浏览器先询问服务器,当前网页所在的域名是否在服务器许可名单内,以及可以使用哪些HTTP动词和头字段。
只有得到肯定的答复浏览器才会发出正式的XHR请求,否则会报错
下面是一段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请求的方法是OUT,并且发送一个自定义的消息头,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,表示来自哪个源,还有两个特殊字段
Access-Control-Request-Method:表示浏览器的CORS请求会用到哪些http方法
Access-Control-Request-Headers:表示浏览器的CORS请求会额外发送的头信息字段。
预检请求的回应:
服务器收到"预检"请求之后,
检查了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字段,表示某个url可以请求数据。
该字段也可以设为" * ",表示同意任意跨源请求
如果浏览器否定了" 预检 "请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,
浏览器就会认定,服务器不允许跨源请求,因此触发一个错误,被XHR对象的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,
必须,是一个逗号分隔字符串,表示服务器支持的跨域请求方法
Access-Control-Allow-Headers,
如果请求包含了Access-Control-Request-Headers,
则此字段必须是一个逗号分隔的字符串,表示服务器所支持的所有头信息字段
Access-Control-Allow-Credentials,
与请求时的含义相同
Access-Control-Max-Age,
可选,用来指定本次预检的有效期,单位为秒。
浏览器正常的请求和回应
一旦服务器通过了"预检"请求,则以后每次浏览器正常的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
字段是每次回应都必定包含的。
JSONP跨域
jsonp跨域基本原理:
由于 script 标签不受浏览器同源策略的影响,允许跨域引用资源。
因此可以通过动态创建script标签,然后利用 src 属性进行跨域。
通过下面的例子来说明jsonp实现跨域的流程:
1 // 1. 定义一个 回调函数 handleResponse 用来接收返回的数据 2 function handleResponse(data) { 3 console.log(data); 4 }; 5 6 // 2. 动态创建一个 script 标签,并且告诉后端回调函数名叫 handleResponse 7 var body = document.getElementsByTagName('body')[0]; 8 var script = document.gerElement('script'); 9 script.src = 'http://www.laixiangran.cn/json?callback=handleResponse'; 10 body.appendChild(script); 11 12 // 3. 通过 script.src 请求 `http://www.laixiangran.cn/json?callback=handleResponse`, 13 // 4. 后端能够识别这样的 URL 格式并处理该请求,然后返回 handleResponse({"name": "laixiangran"}) 给浏览器 14 // 5. 浏览器在接收到 handleResponse({"name": "laixiangran"}) 之后立即执行 ,也就是执行 handleResponse 方法,获得后端返回的数据,这样就完成一次跨域请求了。
学习笔记,如有错误,请师傅们指出
参考文章: