同源策略
浏览器处于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
解释同源之前先对url
进行解析
同源指的是:
协议相同
域名相同
端口相同
只要不满足上述3个条件的任意一个,即为不同源,也就是我们常说的跨域
同源策略的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据
不同源的几种情况:
协议不同
http://www.a.com/a.jshttps://www.a.com/a.js
域名不同
http://www.a.com/a.jshttp://www.b.com/a.js
端口不同
http://www.a.com:80/a.jshttp://www.a.com:8080/a.js
注:对于当前页面来说页面存放的js
文件的域不重要,重要的是加载该js
页面所在什么域
如果不同源,共有3种行为受到限制
无法读取非同源网页的
Cookie、LocalStorage、IndexedDB
无法接触非同源网页的
DOM
无法向非同源地址发送
Ajax
请求(可以发送,但浏览器会拒绝接受响应)
跨域的几种实现方式
当网站A
需要获取网站B
的数据就涉及到跨域了,本文讲述的跨域方式有以下几种:
1. JSONP(JSON with Padding)
HTML
中的script
标签可以加载其他域下的js
,利用这点我们可以在网页上通过添加一个script
元素,向服务器请求JSON
数据,这种做法不受同源策略的限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来
<body> <div class="container"> <ul class="news"></ul> <button class="show">show news</button> </div> <script> var ul = document.querySelector('.news'); var btn = document.querySelector('.show'); btn.addEventListener('click', function(){ var script = document.createElement('script'); script.setAttribute('src', 'http://127.0.0.1:8080/getNews?callback=appendHtml'); document.head.appendChild(script); document.head.removeChild(script); }) function appendHtml(news){ var html = ''; for(var i = 0; i < news.length; i++){ html += '<li>' + news[i] + '</li>'; } ul.innerHTML = html; } </script></body>
var http = require('http');var fs = require('fs');var path = require('path');var url = require('url'); http.createServer(function(req, res){ var pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/getNews': var news = [ '苹果', '香蕉', '猕猴桃' ]; res.setHeader('Content-type', 'text/json; charset=utf-8'); if(pathObj.query.callback){ res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')'); }else { res.end(JSON.stringify(news)); } break; default: fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){ if(e){ res.writeHead(404, 'not found'); res.end('<h1>404 not found</h1>'); }else { res.end(data); } }) } }).listen(8080);
使用node.js
搭建服务器,响应数据如下所示:
点击按钮时,页面呈现效果如下所示:
JSONP
的最大特点是老式浏览器全部支持,兼容性好,服务器的改造非常小;但缺点是只支持get
请求,且错误处理机制不如XMLHttpRequest
好
2. CORS(Cross-Origin Resource Sharing)
CORS
全称是跨域资源共享,是一种ajax
跨域请求资源的方式,支持现代浏览器,IE
支持10以上。实现方式很简单,当你使用XMLHttpRequest
发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin
,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin
;浏览器判断该响应头中是否包含Origin
的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含,浏览器直接驳回,这时我们无法拿到响应数据。
整个CORS
通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS
通信与同源的ajax
通信没有差别,代码一样。浏览器一旦发现ajax
请求跨源,就会自动添加一些附加的请求头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS
的关键是服务器,只要服务器实现了CORS
接口,就可以跨源通信
<body> <div class="container"> <ul class="news"></ul> <button class="show">show news</button> </div> <script> var ul = document.querySelector('.news'); var btn = document.querySelector('.show'); btn.addEventListener('click', function(){ var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://127.0.0.1:8080/getNews', true); xhr.send(); xhr. = function(){ appendHtml(JSON.parse(xhr.responseText)) } }) function appendHtml(news){ var html = ''; for(var i = 0; i < news.length; i++){ html += '<li>' + news[i] + '</li>'; } ul.innerHTML = html; } </script></body>
var http = require('http');var fs = require('fs');var path = require('path');var url = require('url'); http.createServer(function(req, res){ var pathObj = url.parse(req.url, true); switch(pathObj.pathname){ case '/getNews': var news = [ '苹果', '香蕉', '猕猴桃' ]; res.setHeader('Content-type', 'text/json; charset=utf-8'); res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080'); res.end(JSON.stringify(news)); break; default: fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){ if(e){ res.writeHead(404, 'not found'); res.end('<h1>404 not found</h1>'); }else { res.end(data); } }) } }).listen(8080);
使用node.js
搭建服务器,请求如下所示:
若将Access-Control-Allow-Origin
改为*
,则包含所有,任何网站都可以访问
3. 降域
例如:A
网站的a.html
访问B
网站的b.html
,A
网站的域名a.xxx.com:8080
,B
网站的域名b.xxx.com:8080
,a.html
上嵌入iframe
的b.html
。在同源策略下这两者之间域名不同是不能互相访问的,只有在同源的情况下,父窗口和子窗口才能通信;如果跨域,就无法拿到对方的DOM
。为达到目的,我们可以采取降域措施,将两者的域名设置为document.domain = 'xxx.com'
,完整的代码如下所示a.html
:
<body> <div class="ct"> <h1>使用降域实现跨域</h1> <div class="main"> <input type="text" placeholder="http://a.xxx.com:8080/a.html"> </div> <iframe src="http://b.xxx.com:8080/b.html" frameborder="0"></iframe> </div> <script> var input = document.querySelector('.main input'); input.addEventListener('input', function(){ console.log(this.value); window.frames[0].document.querySelector('input').value = this.value; }); document.domain = 'xxx.com'; </script></body>
b.html
:
<body> <input id="input" type="text" placeholder="http://b.xxx.com:8080/b.html"> <script> var input = document.querySelector('#input'); input.addEventListener('input', function(){ window.parent.document.querySelector('input').value = this.value; }); document.domain = 'xxx.com'; </script></body>
为模拟两个不同域名下的页面互相访问,我们可在本地修改host
,修改如下:
127.0.0.1 a.xxx.com192.168.1.23 b.xxx.com
因此在开始http-server
后,a.html
和b.html
分别对应:
127.0.0.1:8080/a.html a.xxx.com:8080/a.html 192.168.1.23:8080/b.html b.xxx.com:8080/b.html
当在父窗口的输入框中输入字符时,子窗口的输入框也会输入相同的内容,效果如图所示:
注:此方法只适用于一级域名相同,只是二级域名不同的两个窗口,把么就可设置document.domain
属性,规避同源策略
4. postMessage
仍以上个例子举例,父窗口a.xxx.com
向子窗口b.xxx.com
发消息,在不改变domain
的情况下,调用postMessage
方法即可
H5
引入新的API
:window.postMeaasge
,此方法提供了一种受控机制来规避同源策略的限制,只要正确的使用,就可以安全地实现跨源通信,无论这两个窗口是否同源。
语法:
otherWindow.postMessage(message, targetOrigin, [transfer])
otherWindow
:其他窗口的一个引用,比如iframe
的contentWindow
、执行window.open
返回的窗口对象、或者是命名过或数值索引的window.frames
message
:将要发送到其他window
的数据targetOrigin
:通过窗口的origin
属性来指定哪些窗口能接收到消息事件,其值可以是字符串*
(表示无限制)或者一个URI
(协议+域名+端口)transfer
(可选):是一串和message
同时传递的Transferable
对象。这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权
当在父窗口使用postMessage
向子窗口传递消息时,需在子窗口监听message
事件
window.addEventListener('message', receiveMessage, false);function receiveMessage(event){ var origin = event.origin; if(origin !== 'http://b.xxx.com:8080'){ return; }else { console.log(event.data); } }
message
事件的参数是事件对象event
,提供以下3个属性:
event.data
:消息内容event.origin
:调用postMessage
时消息发送方窗口的origin
(协议+域名+端口)event.source
:对发送消息的窗口对象的引用
a.html
:
<body> <div class="ct"> <h1>使用postMessage跨域</h1> <div class="main"> <input type="text" placeholder="http://a.xxx.com:8080/a.html"> </div> <iframe src="http://b.xxx.com:8080" frameborder="0"></iframe> </div> <script> var input = document.querySelector('.ct input'); input.addEventListener('input', function(){ window.frames[0].postMessage(this.value, '*'); console.log(this.value) }); window.addEventListener('message', function(e){ input.value = e.data; console.log(e.data); }); </script></body>
b.html
:
<body> <input id="input" type="text" placeholder="http://b.xxx.com:8080/b.html"> <script> var input = document.querySelector('#input'); input.addEventListener('input', function(){ window.parent.postMessage(this.value, '*'); console.log(this.value) }); window.addEventListener('message', function(e){ input.value = e.data; console.log(e.data); console.log(e.source); console.log(e.origin); }); </script></body>
当在父窗口的输入框中输入字符时,子窗口的输入框也输入相同的内容,如图所示:
打印如下:
作者:饥人谷_Tracy
链接:https://www.jianshu.com/p/7e1031549599