一文系列企图经过一篇简短的文章来梳理一个知识点,在杂碎的时间片断中给本身带来一点点提高。
简单的说,是由于浏览器的同源策略。javascript
同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。(引用于MDN定义)php
若是两个连接的协议、域名、端口都一致,那么这两个URL同源,不然不一样源。html
假设A站点的连接为https://news.a.com/www/index.html
,B站点为下列连接时其同源检测以下表前端
URL | 结果 | 缘由 |
---|---|---|
https://news.a.com/www/.html |
同源 | |
https://news.a.com/mmm/index.html |
同源 | |
https://music.a.com/www/index.html |
不一样源 | 域名不一致 |
http://news.a.com/www/index.html |
不一样源 | 协议不一致 |
https://news.a.com:6666/www/index.html |
不一样源 | 端口不一致 |
同源策略分为如下两种:java
举个栗子,Jarry登录了A站点准备购物,与此同时Jarry正在B站点网上冲浪。若是没有同源策略,那么B站点的脚本能够轻轻松松的修改A站点的DOM结构或者向A站点的服务器发起不恰当的请求,致使存在安全隐患。nginx
IE浏览器有两个意外:web
当A站点与B站点不一样源(只要协议、域名、端口三者其一不一致)时,A站点没法获取到B站点的服务或者数据,此时就产生了跨域。express
上图中,站点https://www.jarrychung.com
企图向不一样源站点https://www.baidu.com
发起GET请求,致使报错。json
须要服务端支持。api
JSON是一种经常使用的数据交换格式,而JSONP是JSON的一种使用模式,能够经过这种模式来进行跨域获取数据。重要的是,JSONP使用简便,没有兼容性问题。
同源策略下,不一样源的站点没法相互获取到数据,但img
/iframe
/script
标签是个例外,这些标签能够经过src
属性获取到不一样源的服务器。当正常的请求一个JSON数据时,服务端会返回JSON格式的数据。当使用JSONP模式发送请求时,服务端返回的是一段可执行的JavaScript代码。
// 举个例子 // 正常请求服务器(https://news.a.com/news?id=666)时,数据以下: {"id": 666,"text":"Jarry Chung"} // JSONP模式请求(https://news.a.com/news?id=666?callback=fn)时,数据以下: fn({"id": 666,"text":"Jarry Chung"}) // 而后使用回调函数即可以处理得到的数据
注意:JSONP只支持GET请求,服务端可能在JSONP响应中夹带恶意代码,判断是否请求成功是困难的。
须要服务端支持。
CORS常常被称为现代化版本的JSONP,可以发起全部种类的HTTP请求,以及拥有良好的错误处理。
跨源资源共享标准的工做原理是添加自定义的HTTP头部,容许服务器描述容许使用Web浏览器读取该信息的起源集。此外,对于可能对服务器数据产生反作用的HTTP请求方法,规范要求浏览器“预检”请求,从而请求支持的方法。服务器使用HTTP OPTIONS
请求方法,而后,在服务器“批准”后,使用实际的HTTP请求方法发送实际请求。服务器还能够通知客户端是否让“凭据”(包括Cookie和HTTP认证数据)与请求一块儿发送(翻译自MDN)。
CORS的基本思想是使用自定义的HTTP头部容许服务端和浏览器互相认识,从而让服务端决定是否容许请求以及响应。
// 举个例子 var exp = require('express'); var app = exp(); app.all('*', function(req, res, next) { // 设置容许的源 res.header("Access-Control-Allow-Origin", "*"); // 设置容许的HTTP请求方式 res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); res.header("Access-Control-Allow-Headers", "X-Requested-With"); res.header("Content-Type", "application/json;charset=utf-8"); next(); }); app.get('/user/:id/:pw', function(req, res) { res.send({id:req.params.id, password: req.params.pw}); }); app.listen(8000);
注意:是最为推荐的方案,但古董级浏览器不支持CORS,如IE8如下的浏览器。
主要在服务端上实现。
浏览器有同源策略,可是服务端没有这个限制,所以能够将请求发送给反向代理服务器,由服务器去请求数据,而后再将数据返回给前端。而前端几乎不须要作任何处理。
server { # 监听80端口,能够改为其余端口 listen 80; # 当前服务的域名 server_name www.a.com; location / { proxy_pass http://www.a.com:81; proxy_redirect default; } # 添加访问目录为/api的代理配置 # 目录为/api开头的请求将被转发到82端口 # 还记得吗,端口不一样也是不一样源 location /apis { rewrite ^/apis/(.*)$ /$1 break; proxy_pass http://www.a.com:82; }
在浏览器实现。
window.name的值是当前窗口的名字,要注意的是每一个iframe都有包裹它的window,而这个window是top window的子窗口,所以它天然也有window.name的属性。window.name属性,若是没有被修改,那么其值在不一样的页面(甚至不一样域名)加载后依旧存在。另外,其值大小一般可达到2MB。
其思想为:在一个页面中内嵌一个iframe标签,由这个iframe进行获取数据,将获取到的数据赋值给window.name属性,而后由页面获取该属性的值。既巧妙的绕过了同源策略,同时该操做也是安全的。
但这里有一个问题,即页面和该页面下的iframe src不一样源的话,这个页面是没法操做iframe的,所以致使取不到name值。
name属性的特性在这时候就很好用了,当前页设置的值, 在页面从新加载(非同域也能够)后, 只要没有被修改,值依然不变。可让iframe的location指向为与页面相同的域,等iframe加载完后页面就能够取到name值了。
<body> <script type="text/javascript"> // 代码参考自:https://www.cnblogs.com/zichi/p/4620656.html function crossDomain(url, fn) { iframe = document.createElement('iframe'); iframe.style.display = 'none'; var state = 0; iframe.onload = function() { if(state === 1) { // 处理数据 fn(iframe.contentWindow.name); // 清楚痕迹 iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } else if(state === 0) { state = 1; // proxy.html为与页面同级的空白页面 iframe.contentWindow.location = 'http://localhost:81/proxy.html'; } }; iframe.src = url; document.body.appendChild(iframe); } // 调用 // 服务器地址 var url = 'http://localhost:82/data.php'; // 处理数据 data就是window.name的值(string) crossDomain(url, function(data) { var data = JSON.parse(iframe.contentWindow.name); console.log(data); }); </script> </body>
主要在浏览器实现,须要服务端支持。
在https://www.a.com/news#JarryChungIsSoCool
这个URL中,location.hash
的值为JarryChungIsSoCool
。由于改变hash
值不会致使刷新页面,所以能够利用location.hash
属性来传递数据。缺点是数据容量以及类型受到限制、数据内容直接暴露出来。
其思想为:若index页面要获取不一样源服务器的数据,那么动态插入一个iframe,将iframe的src属性指向该服务器地址。因为同源策略,此时top window和包裹这个iframe的子窗口还是没法通讯的,所以改变子窗口的路径,将数据看成hash值添加到改变后的路径,而后就可以进行通讯(这一点与利用window.name
跨域的原理几乎一致),可以通讯后能够在iframe中将页面的地址改变,将数据加在index页面地址的hash值上。index页面监听地址的hash值变化便可以取得数据。
在浏览器实现。
该方案适用于主域名一致,子域名不一致的状况。两个页面使用JavaScript将document.domain
设置为相同主域名,从而实现跨域。
<!-- 主页面 a.html --> <iframe src="http://child.domain.com/b.html"></iframe> <script> document.domain = 'domain.com'; var user = 'Jarry Chung'; </script> <!-- 子页面 b.html --> <script> document.domain = 'domain.com'; // 获取父窗口中 user 变量 alert(window.parent.user); // 'Jarry Chung' </script>
在浏览器实现。
postMessage()
是HTML5新增的方法,能够实现跨文本档通讯、多窗口通讯、跨域通讯。示意图以下:index.html
将须要的数据请求发送给iframe或者另外一个页面,iframe或另外一个页面监听到message后响应,取得数据后利用postMessage()
接口将数据返回给index.html
。
postMessage()
有两个参数:
data
:须要传递的数据,HTML5规范中该参数的类型能够是JS的任意基本类型或者可复制的对象,但有部分浏览器只支持传递字符串,所以可能须要将该值处理成字符串后再传递。origin
:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,能够不写。postMessage()
方法只会将message传递给指定窗口,固然若是愿意也能够把参数设置为*
,这样能够传递给任意窗口。须要服务端支持。
WebSocket协议是HTML5一种新的协议,实现了浏览器与服务器全双工通讯,同时容许跨域通信。用法以下:
var ws = new WebSocket('wss://echo.websocket.org'); // 链接打开后发送消息 ws.onopen = function (evt) { console.log('Connection open ...'); ws.send('Hello WebSockets!'); }; // 接受消息后关闭链接 ws.onmessage = function (evt) { console.log('Received Message: ', evt.data); ws.close(); }; // 监听关闭链接 ws.onclose = function (evt) { console.log('Connection closed.'); };
我的经验表示在实战中遇见跨域的状况并很少,可是若是遇见了每每都会掉坑里面。
做为一线开发者,作好知识储备是在须要的时候迅速解决问题的必要条件。