一.浏览器的同源策略
1.什么是同源
- 同协议:如都是http或者https
- 同域名:如都是http://baidu.com/a和http://baidu.com/b
- 同端口:如都是80端口
url | url | 区别 |
---|---|---|
http://baidu.com | https://baidu.com | 协议不同 http和https |
http://baidu.com | http://baidu.com:8080 | 端口不同80和8080 |
http://baidu.com | http://pan.baidu.com | 域名不同 |
2.为什么会有同源策略
有一个小小的东西叫cookie大家应该知道,一般用来处理登录等场景,目的是让服务端知道谁发出的这次请求。如果你请求了接口进行登录,服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中,服务端就能知道这个用户已经登录过了。知道这个之后,我们来看场景:
1.你准备去清空你的购物车,于是打开了买买买网站www.maimaimai.com,然后登录成功,一看,购物车东西这么少,不行,还得买多点。
2.你在看有什么东西买的过程中,你的好基友发给你一个链接www.nidongde.com,一脸yin笑地跟你说:“你懂的”,你毫不犹豫打开了。
3.你饶有兴致地浏览着www.nidongde.com,谁知这个网站暗地里做了些不可描述的事情!由于没有同源策略的限制,它向www.maimaimai.com发起了请求!聪明的你一定想到上面的话“服务端验证通过后会在响应头加入Set-Cookie字段,然后下次再发请求的时候,浏览器会自动将cookie附加在HTTP请求的头字段Cookie中”,这样一来,这个不法网站就相当于登录了你的账号,可以为所欲为了!如果这不是一个买买买账号,而是你的银行账号,那……
这就是传说中的CSRF攻击浅谈CSRF攻击方式。
- 限制Dom的查询
1.有一天你刚睡醒,收到一封邮件,说是你的银行账号有风险,赶紧点进www.yinghang.com改密码。你吓尿了,赶紧点进去,还是熟悉的银行登录界面,你果断输入你的账号密码,登录进去看看钱有没有少了。
2.睡眼朦胧的你没看清楚,平时访问的银行网站是www.yinhang.com,而现在访问的是www.yinghang.com,这个钓鱼网站做了什么呢?
// HTML
<iframe name="yinhang" src="www.yinhang.com"></iframe>
// JS
// 由于没有同源策略的限制,钓鱼网站可以直接拿到别的网站的Dom
const iframe = window.frames['yinhang']
const node = iframe.document.getElementById('你输入账号密码的Input')
console.log(`拿到了这个${node},我还拿不到你刚刚输入的账号密码吗`)
由此我们知道,同源策略确实能规避一些危险,不是说有了同源策略就安全,只是说同源策略是一种浏览器最基本的安全机制,毕竟能提高一点攻击的成本。其实没有刺不穿的盾,只是攻击的成本和攻击成功后获得的利益成不成正比。
二.绕过同源策略---跨域
1.JSONP
原理:
JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现
例:
页面部分
<!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);
})
// 通过 script.src = 'http://127.0.0.1:8080/getNews?callback=appendHtml' 来绕过浏览器的同源策略,
// 向目标服务器发送数据请求。 callback = appendHtml,是与后端数据对接的接口,名称可与后端协商。
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>
服务器部分
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 = [
一家中国官方媒体的微信公众号近日刊登了一篇纪念辽宁舰首次成功起降歼-15舰载机的
文章,因文中有一句“新型航母也已经在船台上有序建造”,而被广泛解读为“官方证实中国
正在建造第 三艘航母”
]
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;
// 后端分析接受到url,如果发现相应关键词/getNews则,则通过res.setHeader对响应头部加上让浏览对
//传输内容以Json数据解析。如果找到关键词 callback,则对传送数据用callback的值包裹,appendHtml(' + // JSON.stringify(news) + '),
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)
2.CORS
跨域资源共享(Cross-Origin Resource Sharing)。
是一种 ajax 跨域请求资源的方式,支持现代浏览器,IE支持10以上。 实现方式很简单,当你使用 XMLHttpRequest 发送请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin,后台进行一系列处理,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin; 浏览器判断该相应头中是否包含 Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果不包含浏览器直接驳回,这时我们无法拿到响应数据。所以 CORS 的表象是让你觉得它与同源的 ajax 请求没啥区别,代码完全一样。
页面代码
<!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>
服务端代码
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 = [
一家中国官方媒体的微信公众号近日刊登了一篇纪念辽宁舰首次成功起降歼-15舰载机的
文章,因文中有一句“新型航母也已经在船台上有序建造”,而被广泛解读为“官方证实中国
正在建造第 三艘航母”
]
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
//res.setHeader('Access-Control-Allow-Origin','*')
// 服务器收到请求,并在响应头加上Access-Control-Allow-Origin和值http://localhost:8080,
// 浏览器接受到服务器的响应,检查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)
三.降域 document.domain
这种方式只适合主域名相同,但子域名不同的iframe跨域
<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.main.com:8080/a.html">
</div>
<iframe src="http://b.main.com:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "main.com"
</script>
</html>
四.postMessage
window.postMessage() 是HTML5的一个接口,专注实现不同窗口不同页面的跨域通讯
<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.main.com:8080/a.html">
</div>
<iframe src="http://localhost:8080/b.html" frameborder="0" ></iframe>
</div>
<script>
// URL: http://a.main.com:8080/a.html
function $(id){
return document.querySelector(id);
}
$('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
})
// 利用postMessage来传送值。相应的接受方,设置message事件来接受。
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
</body>
</script>
</html>
参考
1.https://segmentfault.com/a/1190000015597029
2.http://book.jirengu.com/fe/%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80/Javascript/%E8%B7%A8%E5%9F%9F.html