一、同源策略
同源策略限制从一个源的资源如何与另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键机制。
二、怎样算同源
两个页面的协议、域名和端口完全相同,则两个页面是同源的。
三、同源限制范围
(1)Cookie , LocalStorage, IndexedDB 数据读取限制
(2)DOM无法获取。如iframe 窗口和window.open方法打开的窗口,无法与父窗口通信
(3)Ajax请求发送限制
四、什么是跨域
协议、域名、端口 有一个不同,就是不同域。对于协议和端口不同的跨域,只能通过后台解决。
五、常用跨域的解决方案
(一)、跨域资源共享(CORS)——解决Ajax同源请求限制
概述:CORS允许Web应用服务器进行跨域访问,浏览器支持在API容器中(如XHR或Fetch)中使用CORS。 CORS需要浏览器与服务器的同时支持。目前IE10以上的浏览器都支持了该功能。因此,实现CORS的关键是服务器,必须要服务端实现了CORS接口,才实现跨域通信。浏览器端一旦发现Ajax请求跨域,会自动增加附加头信息。CORS新增了一组http头部字段,允许服务器声明哪些源站有权限访问哪些资源。对可能产生副作用的http请求,浏览器必须首先使用OPTIONS方法发起一个预检请求,在得到服务器允许确认允许后,才发起实际的http请求。
应用场景:——————简单请求、预检请求、带身份凭证(Cookie)的请求,以XHR为例,fetch同理,但按fetch标准略有不同
1、简单请求
//request head
GET /resources/public-data/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/access-control/simpleXSInvocation.html Origin: http://foo.example //response HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 00:23:53 GMT Server: Apache/2.0.61 Access-Control-Allow-Origin: * //*表示可以被任何域访问。如果仅仅允许http://foo.example 则将*替换为该地址即可 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Transfer-Encoding: chunked Content-Type: application/xml
使用Origin 和Access-Control-Allow-Origin 就能完成最简单的访问控制。
2、预检请求
当请求满足下列任一条件时,应该首先发送预检请求:
- 使用了下面任意http方法: put delect connect options trace patch
- 人为设置了CORS安全的首部字段集合之外的其他首部字段。即:
- Accept Accept-Language
- Content-Language
- Content-Type(值非application/x-www-for-urlencoded multipart/form-data text/plain)
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
需要预检的http请求示例:
var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
function callOtherDomain(){
if(invocation)
{
invocation.open('POST', url, true);
invocation.setRequestHeader('X-PINGOTHER', 'pingpong'); //需要预检的http请求
invocation.setRequestHeader('Content-Type', 'application/xml'); //需要预检的http请求
invocation.onreadystatechange = handler;
invocation.send(body);
}
}
预检请求
//request head
OPTIONS /resources/post-here/ HTTP/1.1 //使用options方法的预检请求 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Origin: http://foo.example Access-Control-Request-Method: POST //告知服务器,实际请求将是POST方法 Access-Control-Request-Headers: X-PINGOTHER, Content-Type //告知服务器,请求将携带两个自定义首部字段X-PINGOTHER 和Content-Type。 服务器据此决定该请求是否被允许
//response
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://foo.example //允许的域为http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS //允许使用POST GET OPTIONS发送请求
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type //允许头部字段中携带 X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400 //改响应的有效时间为86400s。在有效期内浏览器无需为同一请求发起预检请求
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
预检请求通过后发送实际请求
//request head
POST /resources/post-here/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive X-PINGOTHER: pingpong Content-Type: text/xml; charset=UTF-8 Referer: http://foo.example/examples/preflightInvocation.html Content-Length: 55 Origin: http://foo.example Pragma: no-cache Cache-Control: no-cache <?xml version="1.0"?><person><name>Arun</name></person> //response HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:15:40 GMT Server: Apache/2.0.61 (Unix) Access-Control-Allow-Origin: http://foo.example Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 235 Keep-Alive: timeout=2, max=99 Connection: Keep-Alive Content-Type: text/plain [Some GZIP'd payload]
3、带身份凭证(Cookie)的请求
对于跨域的XHR和Fetch请求,浏览器不会发送身份凭证信息,如需发送,需要设置XHR的特殊标记withCredentials
带Cookie的跨域请求示例
var invocation = new XMLHttpRequest(); var url = 'http://bar.other/resources/credentialed-content/'; function callOtherDomain(){ if(invocation) { invocation.open('GET', url, true); invocation.withCredentials = true; //携带cookie发送请求,服务端需要携带Access-Control-Allow-Credentials:ture, 否则浏览器不会把响应给请求发送者 invocation.onreadystatechange = handler; invocation.send(); } }
携带Cookie的请求响应
//request head GET /resources/access-control-with-credentials/ HTTP/1.1 Host: bar.other User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Connection: keep-alive Referer: http://foo.example/examples/credential.html Origin: http://foo.example Cookie: pageAccess=2 //response HTTP/1.1 200 OK Date: Mon, 01 Dec 2008 01:34:52 GMT Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2 X-Powered-By: PHP/5.2.6 Access-Control-Allow-Origin: http://foo.example Access-Control-Allow-Credentials: true Cache-Control: no-cache Pragma: no-cache Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 106 Keep-Alive: timeout=2, max=100 Connection: Keep-Alive Content-Type: text/plain
注:带身份凭证的请求Access-Control-Allow-Origin 值不可设置为“*”,需要指明具体值,否则请求会失败。
(二)、JSONP
JSONP由两部分组成:
- 回调函数 当响应来到时页面中该调用的函数,回调函数名在请求中指定
- 数据 传入回调函数中的JSON数据
JSONP跨域步骤:
1、浏览器构造请求地址
标准的script请求地址为: 请求的资源地址 + 获取函数的字段名 + 回调函数名。 获取函数的字段名需要客户端服务端共同约定,如jquery中默认为callback。
function resolveJson(result) { console.log(result.name); } var jsonpScript= document.createElement("script"); jsonpScript.type = "text/javascript"; jsonpScript.src = "http://www.qiute.com?callbackName=resolveJson"; //callbackName 获取函数的字段名 resolveJson回调函数 document.getElementsByTagName("head")[0].appendChild(jsonpScript);
2、服务端构造返回
在收到浏览器的请求后,从url中按约定的获取函数字段寻找回调函数名,即按照callbackName找到了回调函数为resolveJson,然后将数据传入回调函数
resolveJson({name: 'qiutc'});
3、客户端以脚本方式执行客户端返回值 客户端拿到回调数据后,自行处理
参考:
JavaScript高级程序设计
https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy 浏览器的同源策略
http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html 浏览器的同源策略及其规避方法
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS http访问控制
https://segmentfault.com/a/1190000006095018 浏览器跨域方法与基于Fetch的Web请求最佳实践
https://segmentfault.com/a/1190000000718840 详解js跨域问题
https://github.com/camsong/blog/issues/2 fetch简介