同源策略(Same origin Policy)
是什么?
浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
同源是什么?
如果协议,端口(如果指定了一个)和域名对于两个页面是相同的,则两个页面具有相同的源。
需要注意的是: 对于当前页面来说页面存放的 JS 文件的域不重要,重要的是加载该 JS 页面所在什么域
报错范例
当我们手写一个服务器,并启动,代码、显示结果如下。。。
server.js
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 '/getWeather':
console.log("get weather");
res.end(JSON.stringify({beijing: 'sunny'}))
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(8011)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>饥人谷</h1>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET','http://localhost:8011/getWeather', true)
xhr.send()
xhr.onload = function(){
console.log(xhr.responseText)
}
</script>
</body>
</html>
当我们修改index.html 中
xhr.open('GET','http://localhost:8011/getWeather', true)
# xhr.open('GET','http://127.0.0.1:8011/getWeather', true)
因为不是同源,所以会报错。
那我们的请求有没有发出去?答案是有的,看我们上面的代码,我们写了console.log("get weather")说明请求发出去了,所以终端会打印get weather
那我们请求发出去,有没有响应呢?别急继续往下看
也响应了,但是浏览器不接收,因为浏览器认为这个是不安全的,所以同源策略是浏览器的安全机制。
总之,同源策略就是当页面向接口发请求的时候,页面的URL和接口的URL是同源的。
JSONP
如果我们的页面向接口请求的话,假设是不同源的,我们该怎么做?
举个例子,我们手下有两个网站,A网站和B网站,A网站想使用B网站的某个接口获取数据,即使这两个网站在同一个服务器上,浏览器也是会阻止掉的,所以我们引用了JSONP。
是什么?
JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现。
JSONP举例
server.js
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 = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心"
]
/*防止中文乱码*/
res.setHeader('Content-Type','text/json; charset=utf-8')
/*下面是后端为jsonp实现要做的事情*/
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(8088)
index,html
<!DOCTYPE html>
<html>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script>
$('.show').addEventListener('click', function(){
var script = document.createElement('script');
script.src = 'http://127.0.0.1:8088/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>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>
注意上面的代码index.html的script.src要和server,js监听端口号一致,否则会出现下面问题
正常应该是这样显示
注意当前页面是localhost:8088/index.html,而我们请求的是127.0.0.1:8088/index.html
JSONP是比较HACK的跨域方法,现在不常使用
CORS(Cross-Origin Resource Sharing)
是什么?
CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。
CORS举例
server.js
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 = [
"第11日前瞻:中国冲击4金 博尔特再战200米羽球",
"正直播柴飚/洪炜出战 男双力争会师决赛",
"女排将死磕巴西!郎平安排男陪练模仿对方核心"
]
res.setHeader('Access-Control-Allow-Origin','http://localhost:8081')
//res.setHeader('Access-Control-Allow-Origin','*')
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(8081)
index.html
<!DOCTYPE html>
<html>
<body>
<div class="container">
<ul class="news">
</ul>
<button class="show">show news</button>
</div>
<script>
$('.show').addEventListener('click', function(){
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8081/getNews', true)
xhr.send()
xhr.onload = function(){
appendHtml(JSON.parse(xhr.responseText))
}
})
function appendHtml(news){
var html = ''
for( var i=0; i<news.length; i++){
html += '<li>' + news[i] + '</li>'
}
$('.news').innerHTML = html
}
function $(selector){
return document.querySelector(selector)
}
</script>
</html>
第一种是正常情况
第二种就是跨域
加了一个声明,Access-Control-Allow-Origin,允许哪些域使用我的数据。
JSONP和CORS本质是从不同域的接口获取数据,下面我们要说一下如何iframe跨域。
降域
比如当前页面下,我们埋了一个iframe,iframe对应的地址是taobao.com,假设你刚刚用电脑访问过淘宝,买了一些东西,然后又访问这个页面,而这个iframe会自动加载,并且是登录状态。假设浏览器没有做一些安全限制,那在当前页面下,我们是可以使用JavaScript来操控iframe上的内容,我们就可以知道淘宝用户是谁,当前金额是多少,我们甚至可以操作模拟用户的点击,所以浏览器不会让你这么做,所以浏览器想到一个办法,只有是当前域名的iframe,你才可以访问里面的东西。换句话说,我们的页面埋下的iframe页面,如果是不同域,虽然可以加载,但是外部的JavaScript是无法操作里的内容。
举例
a.html
<html>
<head>
<meta charset="utf-8">
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
</head>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.com:8080/a.html">
</div>
<iframe src="http://b.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.jrg.com:8080/a.html
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"
</script>
</html>
b.html
<html>
<head>
<meta charset="utf-8">
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="http://b.jrg.com:8080/b.html">
<script>
// URL: http://b.jrg.com:8080/b.html
document.querySelector('#input').addEventListener('input', function(){
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'jrg.com';
</script>
</body>
</html>
上图分为左右框,右框为iframe页面,我们这时操作iframe,出现报错
因为当前页面和ifame页面是不同域的。
当我们修改使其为同一域
假使当前页面a.jrg.com ,而iframe为b.jrg.com ,这时我们使用document.domain='jrg.com' 这样就可以操作iframe了,这就是通过降域实现操作iframe
postMessage
假设现在我们有一个页面,我写的页面是在我的域名下。我希望我这个页面可以供其他人使用。其他人可以把我的页面作为iframe页面嵌入到自己的页面当中去。我还希望他们可以在他们的页面上操作我的页面,因为我愿意提供自己的页面,可是按照同源机制,只要域名不一样,他们就不可以操作我的页面,除非我们的主域是相同的,这时该怎么办呢?需要用到postmessage
是什么?
postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
postMessage(data,origin)方法接受两个参数
1.data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
2.origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。
比如说我们的网站嵌入一个官网叫mychat,我希望我的页面和它里面的内容进行交互,mychat就可以支持postmessage,我就可以向他post我的消息,它收到之后,做对应的事情,它给我一个文档告诉我怎么做。
举例
a.html
<html>
<head>
<meta charset="utf-8">
<style>
.ct{
width: 910px;
margin: auto;
}
.main{
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input{
margin: 20px;
width: 200px;
}
.iframe{
float: right;
}
iframe{
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
</head>
<body>
<div class="ct">
<h1>使用postMessage实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>
<iframe src="http://localhost:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
//URL: http://a.jrg.com:8080/a.html
$('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</body>
</script>
</html>
b.html
<html>
<head>
<meta charset="utf-8">
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="http://b.jrg.com:8080/b.html">
<script>
// URL: http://b.jrg.com:8080/b.html
$('#input').addEventListener('input', function(){
window.parent.postMessage(this.value, '*');
})
window.addEventListener('message',function(e) {
$('#input').value = e.data
console.log(e.data);
});
function $(id){
return document.querySelector(id);
}
</script>
</body>
</html>
参考资料:同源策略
postmessage