同源策略(Same origin Policy)
正规解读:
同源策略是浏览器出于安全方面的考虑,只允许与本域下的接口交互。
不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源。
请求的页面和当前发出请求的页面是同域的情况,这就是属于浏览器的安全策略
白话解读:
同源策略就是看你执行js的当前页面的url 和请求的页面的url 是否同协议 同域名 同端口 也就是是否同源。
举个例子:
我给网页中嵌入 你支付宝iframe ,做一个钓鱼网页,这个时候你的支付宝是登陆状态,那么我就可以操控你的支付宝。但是支付宝的域和你当前的钓鱼网页的域是不同的那么也就是同源机制来防止这种而已操作。
什么是同源?:
同协议:如都是http或者https
同域名:如都是http://baidu.com/a 和http://baidu.com/b
同端口:如都是80端口
如:
http://jirengu.com/a/b.js 和 http://jirengu.com/index.php (同源)
不同源的例子:
http://jirengu.com/main.js 和 https://jirengu.com/a.php (协议不同)
http://jirengu.com/main.js 和 http://bbs.jirengu.com/a.php (域名不同,域名必须完全相同才可以)
http://jiengu.com/main.js 和 http://jirengu.com:8080/a.php (端口不同,第一个是80)
注意:
对于当前页面来说页面存放的JS 文件的域不重要,重要的是加载该 JS 页面所在什么域(即当前页面的执行环境 比如我可以引入百度的js或者css,域是当前js或者css执行的url和你请求的页面的url)
报错范例:
node server.js 用浏览器访问http://localhost:8080/index.html
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':
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(8080)
index.html
<h1>饥人谷</h1>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET','http://localhost:8080/getWeather', true)
xhr.send()
xhr.onload = function(){
console.log(xhr.responseText)
}
</script>
什么是跨域?
跨域就是我们是否能绕过同源策略去获取数据
我写了一个页面我不能通过ajax获取其他网站接口的数据,除非他们允许 ,否则就会被浏览器限制住报错。
举个例子:我说中文 你说英文, 我想在中文文章中引用英文是允许的 但是为我想写一篇英文文章就不允许。因为运行的域是英文 域,此时就需要跨域了
解释下引用英文是什么含义:就是我们引用其他域名下的js或者css都是可以的 但是操作他们不行。
跨域的几种方法
跨域的第一种方法: JSONP
HTML 中 script 标签可以加载其他域下的js,比如我们经常引入一个其他域下线上cdn的jQuery。那如何利用这个特性实现从其他域下获取数据呢?
换句话说script src 可以去请求任何网站对应的数据
可以先这样试试:
<script src="http://api.jirengu.com/weather.php"></script> //把接口放在src里
浏览器会把接口的数据当作js执行
这时候会向天气接口发送请求获取数据,获取数据后做为 js 来执行。 但这里有个问题, 数据是 JSON 格式的数据,直接作为 JS 运行的话我如何去得到这个数据来操作呢?
这样试试:
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
这个请求到达后端后,后端会去解析callback这个参数获取到字符串showData,在发送数据做如下处理:
之前后端返回数据: {"city": "hangzhou", "weather": "晴天"}
现在后端返回数据: showData({"city": "hangzhou", "weather": "晴天"})
前端script标签在加载数据后会把 「showData({“city”: “hangzhou”, “weather”: “晴天”})」
做为 js 来执行,这实际上就是调用showData这个函数,同时参数是 {“city”: “hangzhou”, “weather”: “晴天”}
。 用户只需要在加载提前在页面定义好showData这个全局函数,在函数内部处理参数即可。
<script>
function showData(ret){
console.log(ret);
}
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
这就是 JSONP(JSON with padding)
总结一下:
JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现。
优点:使用了script没有使用ajax 绕过了 同源策略
问题:是需要后端支持callback(可变)
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')
if(pathObj.query.callback){ //如果他有callback这个参数
res.end(pathObj.query.callback + '(' + JSON.stringify(news) + ')')
}else{//后端做的就是这块 判断是否有callback这个参数
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)
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: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>';
}
console.log(html);
$('.news').innerHTML = html;
}
function $(id){
return document.querySelector(id);
}
</script>
</html>
打开终端,输入 node server.js ,浏览器打开 http://localhost:8080/index.html
跨域的第二种方式: CORS
cors是对ajax功能的扩展。让ajax本身支持
CORS 全称是跨域资源共享(Cross-Origin Resource Sharing),是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。
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:8080')
//后端允许的是localhost:8080
//浏览器搜到这个响应头 会进行比较 比较当前的origin 和你允许的origin是否一样
//如果一样 浏览器就会放过这个请求 得到数据 如果不一样拒绝
//即:后端我允许哪些域
//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(8080)
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:8080/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>
启动终端,执行 node server.js ,浏览器打开 http://localhost:8080/index.html ,查看效果和网络请求
前两种跨域方法类似于不同域下的接口去获取数据
第三种方法:降域
只要当前两个域名后缀一样那么我们给防止iframe的当前页面加上doucment.domain
实现降域从而可以对iframe
中的网页进行操作
a.html
<html>
<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>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>
<iframe src="http://b.jrg.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>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<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>
</html>
第四种方法:postMessage
降域缺点就是大部分都是主域名不相同
postMessage可以不受主域名限制 其特点就是
当我们需要操作一个iframe的时候,我们向当前的iframe postMessage一个消息,只要你这个iframe愿意处理这个消息,那么你给我一个响应。而不是像之前那样直接进行操作。
a.html
<html>
<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>
<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);
}
</script>
</html>
b.html
<html>
<style>
html,body{
margin: 0;
}
input{
margin: 20px;
width: 200px;
}
</style>
<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>
</html>