同源策略及限制
同源策略的概念
- 同源:http協(xié)議署浩,域名癣亚, 端口三者均相同
- 同源策略是用來限制在一個(gè)源上加載的文檔或腳本與來自另一個(gè)源的資源進(jìn)行交互哺眯。這是一個(gè)用于隔離潛在惡意文件的關(guān)鍵的安全機(jī)制陕见。
同源策略的限制
- cookie localStorage indexDB 無(wú)法讀取
- dom 無(wú)法獲得 ajax請(qǐng)求不能發(fā)送
前后端通信的常見幾種方式
- Ajax(同源通信)
- WebSocket(協(xié)議不同的不同源通信)
- CORS(用于支持不同源之間ajax通信的方法)
Ajax通信
Ajax 概念
- Ajax(Async JavaScript And XML)是一種依賴CSS/HTML/JAVASCRIPT 等現(xiàn)有技術(shù)使用XMLHttpRequest
對(duì)象發(fā)送http 請(qǐng)求并接受響應(yīng)的一種技術(shù)方案
實(shí)現(xiàn)一個(gè)Ajax
/**
* {string} param.url
* {string} param.type? || 'get'
* {object} param.data
* {function} param.success
* {function} param.error
*/
var ajax = function(param) {
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP")
var type = (param.type || 'get').toUpperCase()
var url = param.url
if(!url) return
var data = param.data
var dataArr = []
for (var k in data) {
dataArr.push(k + '=' + data[k])
}
if (type === 'GET') {
url = url + '?' + dataArr.join('&')
xhr.open(type, url)
xhr.send()
}
if (type === 'POST') {
xhr.open(type, url)
xhr.setRequestHeader("Content-type", "application/x-www.form-urlencoded")
xhr.send(dataArr.join('&'))
}
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200 || xhr.status === 304) {
var res
if (param.success && typeof param.success === 'function') {
res = xhr.responseText
if (typeof res === 'string') {
res = JSON.parse(res)
param.success.call(xhr, res)
}
}
} else {
param.error.call()
}
}
}
}
跨域通信的幾種方式
- 首先我們先給host 設(shè)置幾個(gè)子域名來模擬跨域
跨域代碼示例
$ npm install
$ npm start
port: 3000
使用示例前記得設(shè)置本機(jī)host
JSONP
jsonp 原理就是在頁(yè)面上動(dòng)態(tài)添加一個(gè)script標(biāo)簽世剖,給標(biāo)簽的src 指定一個(gè)url 路徑并加上回調(diào)函數(shù)query 參數(shù)定罢,發(fā)送給后端后,后端利用需返回的數(shù)據(jù)和回調(diào)函數(shù)的query 參數(shù)拼接成類似handleJsonp({ a:1, b:2 })
的字符串返回前端旁瘫,前端定義的handleJsonp 的函數(shù)會(huì)直接運(yùn)行并處理{ a:1, b:2 }
這個(gè)后端返回的數(shù)據(jù)
- 只能發(fā)送GET請(qǐng)求
- 可能會(huì)被注入惡意代碼 callback=alter('111')
- 任何域都可以發(fā)送jsonp請(qǐng)求,需進(jìn)行驗(yàn)證琼蚯,如token
// 前端代碼
jsonpBtn.addEventListener('click', function() {
const script = document.createElement('script')
script.src = 'http://b.yang.com:3000/jsonp?callback=handleJsonp'
document.head.appendChild(script)
// document.head.removeChild(script)
})
function handleJsonp(data) {
console.log(data)
}
// 后端代碼
// JSONP
router.get('/jsonp', function(req, res, next) {
let { callback: cb } = req.query
const data = {
type: 'jsonp',
data: 'data'
}
cb = cb.replace(/\(/g, ''); // 替換掉() 防止惡意代碼注入
cb = cb.replace(/\)/g, '');
res.send(cb + '(' + JSON.stringify(data) + ')')
})
CORS
- CORS(cross-origin resource sharing) 跨域資源共享酬凳,是一種ajax 跨域請(qǐng)求資源的方式, 普遍用于前后端分離開發(fā)環(huán)境
- 原理就在于Access-Control-Allow-Origin 響應(yīng)頭遭庶,它指定瀏覽器在何種域下發(fā)送的ajax 請(qǐng)求服務(wù)器資源時(shí)可以跨域
- 服務(wù)器響應(yīng)還可以設(shè)置其它header:
1Access-Control-Allow-Methods: POST, GET, OPTIONS
表明服務(wù)器允許客戶端使用 POST, GET 和 OPTIONS 方法發(fā)起請(qǐng)求
2Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
表明服務(wù)器允許請(qǐng)求中攜帶字段 X-PINGOTHER 與 Content-Type
3Access-Control-Max-Age: 86400
表明該響應(yīng)的有效時(shí)間為 86400 秒
4Access-Control-Allow-Credentials: true
表明跨域請(qǐng)求允許攜帶cookie
MDN
// 前端代碼
cors.addEventListener('click', function() {
let reqHeaders = new Headers()
reqHeaders.append('Content-Type', 'application/x-www-form-urlencoded')
fetch('http://b.yang.com:3000/cors/', {
method: 'post',
headers: reqHeaders,
mode: 'cors',
body: 'post body'
}).then(function (response) {
console.log(response)
})
})
// 后端代碼
// CORS
router.post('/cors', function(req, res, next) {
// res.header('Access-Control-Allow-Origin', 'http://a.yang.com:3000')
res.header('Access-Control-Allow-Origin', '*')
res.send('cors ok')
})
WebSocket
利用websocket 協(xié)議進(jìn)行前后端跨域通信
// 前端代碼
var ws
socket.addEventListener('click', function() {
ws = new WebSocket(`ws://b.yang.com:3000/`)
ws.onmessage = (data) => console.log(data);
ws.onerror = () => console.log('WebSocket error');
ws.onopen = () => console.log('WebSocket connection established');
ws.onclose = () => console.log('WebSocket connection closed');
})
sendmsg.addEventListener('click', function() {
ws.send('send a msg')
})
// 后端代碼
var express = require('express');
var app = express();
const WebSocket = require('ws')
var server = http.createServer(app);
const wss = new WebSocket.Server({ server })
wss.on('connection', (ws, req) => {
ws.on('message', message => {
console.log(message)
ws.send(message)
})
})
server.listen(3000)
降域(使用iframe)
// URL: http://a.yang.com:3000/a
<div class="ct">
<h1>使用降域?qū)崿F(xiàn)跨域</h1>
<div class="main">
<h4>URL: http://a.yang.com:3000/a</h4>
<input type="text" placeholder="http://a.yang.com:3000/a">
</div>
<iframe src="http://b.yang.com:3000/b" frameborder="0" ></iframe>
</div>
<script>
document.querySelector('.main input').addEventListener('input', function(){
console.log(location.host, this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "yang.com"
</script>
// URL: http://b.yang.com:3000/b
<input id="input" type="text" placeholder="http://b.yang.com:3000/b">
<script>
document.querySelector('#input').addEventListener('input', function(){
console.log(location.host, this.value);
window.parent.document.querySelector('input').value = this.value;
})
document.domain = 'yang.com';
</script>
postMessage(使用iframe)
//URL: http://a.yang.com:3000/c
<div class="ct">
<h1>使用postMessage實(shí)現(xiàn)跨域</h1>
<div class="main">
<h4>URL: http://a.yang.com:3000/c</h4>
<input type="text" placeholder="http://a.yang.com:3000/c">
</div>
<iframe src="http://localhost:3000/d" frameborder="0" ></iframe>
</div>
<script>
var input = document.querySelector('.main input')
input.addEventListener('input', function(){
console.log('a.yang.com - input event value', this.value);
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e) {
input.value = e.data
console.log('a.yang.com - message event value', e.data);
});
</script>
// URL: http://b.yang.com:3000/d
<input id="input" type="text" placeholder="http://b.yang.com:3000/d">
<script>
var input = document.querySelector('#input')
input.addEventListener('input', function(){
console.log('b.yang.com - input event value', this.value);
window.parent.postMessage(this.value, '*');
})
window.addEventListener('message',function(e) {
input.value = e.data
console.log('b.yang.com - message event value', e.data);
});
</script>
其他hack
改變hash值