同源策略(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('404 Not Found
')
}else{
res.end(data)
}
})
}
}).listen(8011)
index.html
Title
饥人谷
当我们修改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('404 Not Found
')
}else{
res.end(data)
}
})
}
}).listen(8088)
index,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('404 Not Found
')
}else{
res.end(data)
}
})
}
}).listen(8081)
index.html
第一种是正常情况
第二种就是跨域
加了一个声明,Access-Control-Allow-Origin,允许哪些域使用我的数据。
JSONP和CORS本质是从不同域的接口获取数据,下面我们要说一下如何iframe跨域。
降域
比如当前页面下,我们埋了一个iframe,iframe对应的地址是taobao.com,假设你刚刚用电脑访问过淘宝,买了一些东西,然后又访问这个页面,而这个iframe会自动加载,并且是登录状态。假设浏览器没有做一些安全限制,那在当前页面下,我们是可以使用JavaScript来操控iframe上的内容,我们就可以知道淘宝用户是谁,当前金额是多少,我们甚至可以操作模拟用户的点击,所以浏览器不会让你这么做,所以浏览器想到一个办法,只有是当前域名的iframe,你才可以访问里面的东西。换句话说,我们的页面埋下的iframe页面,如果是不同域,虽然可以加载,但是外部的JavaScript是无法操作里的内容。
举例
a.html
使用降域实现跨域
b.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
使用postMessage实现跨域
b.html
参考资料:同源策略
postmessage