為什么會(huì)存在同源策略
概念
1995年有Netscape公司引入的一個(gè)安全策略,現(xiàn)在所有瀏覽器都在使用這個(gè)策略。它限制了一個(gè)源從另外一個(gè)源請(qǐng)求資源,用于隔離潛在惡意文件伐割。
什么才是不同的源
我們都知道一般的地址又下面三部分組成
- 協(xié)議相同
- 域名相同
- 端口相同
只要兩個(gè)地址其中有一個(gè)不相同候味,那么這兩個(gè)地址就是不同的源。
舉個(gè)例子
//地址
http://www.address.com/item/page.html
協(xié)議: http://
域名:www.example.com
端口:8080(http)/443(https) (端口默認(rèn)省略)
http://www.address.com/item2/other.html:同源
http://address.com/item/other.html:不同源(域名不同)
http://v2.www.address.com/item/other.html:不同源(域名不同)
http://www.address.com:81/item/other.html:不同源(端口不同)
同源的目的
目的是為了保護(hù)用戶信息的安全隔心,防止惡意網(wǎng)站竊取數(shù)據(jù)白群,否則Cookie可以共享。有的網(wǎng)站一般會(huì)把一些重要信息存放在cookie或者LocalStorage中硬霍,這時(shí)如果別的網(wǎng)站能夠獲取獲取到這個(gè)數(shù)據(jù)帜慢,可想而知,這樣就沒(méi)有什么安全可言了唯卖。
限制范圍
目前共有三種行為受到限制
- Cookie粱玲、LocalStorage和IndexDB 無(wú)法讀取
- DOM無(wú)法獲得
- AJAX 請(qǐng)求不能發(fā)送
解決方案
1、通過(guò)jsonp跨域
原理
script拜轨、img抽减、iframe等標(biāo)簽的src屬性都擁有跨域請(qǐng)求資源的能力,我們可以把js撩轰、css胯甩、img等資源放到一個(gè)獨(dú)立域名的服務(wù)器上昧廷。然后通過(guò)動(dòng)態(tài)創(chuàng)建script標(biāo)簽堪嫂,再請(qǐng)求一個(gè)回調(diào)函數(shù)的網(wǎng)址,服務(wù)器把需要傳遞的參數(shù)塞入這個(gè)回調(diào)函數(shù)中木柬, 然后我們?cè)趈s代碼中執(zhí)行這個(gè)函數(shù)就可已獲取到你想要的參數(shù)皆串。
前端原生實(shí)現(xiàn)
<script>
window.xxx = function (value) {
console.log(value)
}
var script = document.createElement('script')
script.src = 'https://www.address.com:433/json?callback=xxx'
document.body.appendChild(script)
</script>
jquery實(shí)現(xiàn)
$.ajax({
type: "get",
url: "https://www.address.com:433/json",
dataType: "jsonp",
jsonp: "callback",
jsonpCallback:"xxx"http://自定義回調(diào)函數(shù)名
success: function(res){
console.log(res)
},
error: function(){
console.log('fail');
}
});
jsonp插件
//安裝
npm install jsonp
const jsonp = require('jsonp');
jsonp('https://www.address.com:433/json', {parma:'xxx'}, (err, data) => {
if (err) {
console.error(err.message);
} else {
console.log(data);
}
});
node.js egg 服務(wù)端
//需要看egg官網(wǎng)構(gòu)建一個(gè)simple框架
egg-init --type=simple
// router.js 用egg內(nèi)置jspnp方法 https://eggjs.org/api/Config.html#jsonp
module.exports = app => {
app.get('/json', app.jsonp({ callback: 'xxx' }), app.controller.json.index)
}
缺點(diǎn)
- 只支持GET請(qǐng)求,不支持POST請(qǐng)求
- 調(diào)用失敗沒(méi)有HTTP狀態(tài)碼
- 不夠安全眉枕。
2恶复、CORS
原理
CORS(Cross-Origin Resource Sharing)跨資源分享,瀏覽器不能低于IE10,服務(wù)器支持任何類型的請(qǐng)求速挑。
熟悉幾個(gè)后臺(tái)需要設(shè)置的字段
- Access-Control-Allow-Origin
字段必傳谤牡,為*表示允許任意域名的請(qǐng)求。當(dāng)有cookie需要傳遞時(shí)姥宝,需要指定域名翅萤。
- Access-Control-Allow-Credentials
字段可選,默認(rèn)為false,表示是否允許發(fā)送cookie腊满。若允許套么,通知瀏覽器也要開(kāi)啟cookie值的傳遞。
- Access-Control-Expose-Headers
字段可選碳蛋。如果想要瀏覽器拿到getResponesHeader()其他字段胚泌,就在這里指定。
- Access-Control-Request-Method
必須字段肃弟,非簡(jiǎn)單請(qǐng)求時(shí)設(shè)置的字段玷室,例如PUT請(qǐng)求零蓉。
- Access-Control-Request-Headers
指定額外的發(fā)送頭信息,以逗號(hào)分割字符串穷缤。
前端原生代碼
var xhr = new XMLHttpRequest()
// 設(shè)置攜帶cookie
xhr.withCredentials = true;
xhr.open('POST', 'https://www.address.com:433/json', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(null)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(JSON.parse(xhr.responseText).msg)
}
}
后端代碼
module.exports = app => {
class CrosController extends app.Controller {
* index(req) {
this.ctx.set('Access-Control-Allow-Origin', 'https://www.address.com');
this.ctx.set('Access-Control-Allow-Credentials', 'true')
this.ctx.body = { msg: 'hello world' }
}
}
return CrosController
}
優(yōu)點(diǎn)
- 支持所有類型的HTTP請(qǐng)求壁公。
缺點(diǎn)
- 不兼容IE10以下。
3绅项、iframe結(jié)合locaction.hash方法
原理
也是利用iframe可以在不同域中傳值的特點(diǎn)紊册,而location.hash正好可以攜帶參數(shù),所以利用iframe作為這個(gè)不同域之間的橋梁快耿。
具體實(shí)現(xiàn)步驟
- 1囊陡、向A域名頁(yè)面插入一個(gè)B域名的iframe標(biāo)簽。
- 2掀亥、然后在B域名的iframe頁(yè)面中ajax請(qǐng)求同域名的服務(wù)器撞反。
- 3、iframe頁(yè)面拿到數(shù)據(jù)后通過(guò)praent.location.href將想要傳遞的參數(shù)放到#后面搪花,作為hash傳遞遏片。
- 4、這樣在A域名頁(yè)面就能通過(guò)window.onhashchange 監(jiān)聽(tīng)處理你想要的數(shù)據(jù)撮竿。
A域名頁(yè)面
var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/hash.html'
document.body.appendChild(iframe)
window.onhashchange = function () {
//處理hash
console.log(location.hash)
}
B域名頁(yè)面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var res = JSON.parse(xhr.responseText)
console.log(res.msg)
parent.location.href = `http://www.A.com:80/a.html#msg=${res.msg}`
}
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)
缺點(diǎn)
- iframe雖然能解決問(wèn)題吮便,但是安全風(fēng)險(xiǎn)還是比較重要的。
- hash傳參處理起來(lái)比較麻煩幢踏。
4髓需、iframe結(jié)合window.name方法
原理
原理其實(shí)是和上面的方法一樣,區(qū)別在于window.name能夠傳遞2MB以上的數(shù)據(jù)房蝉。
A域名頁(yè)面
var iframe = document.createElement('iframe')
iframe.src = 'http://www.B.com:80/name.html'
document.body.appendChild(iframe)
var times = 0
iframe.onload = function () {
if (times === 1) {
console.log(JSON.parse(iframe.contentWindow.name))
destoryFrame()
} else if (times === 0) {
times = 1
}
}
// 獲取數(shù)據(jù)以后銷毀這個(gè)iframe僚匆,釋放內(nèi)存;
function destoryFrame() {
document.body.removeChild(iframe);
}
B域名頁(yè)面
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
window.name = xhr.responseText
location.
}
}
xhr.open('GET', 'http://www.B.com:80/json', true)
xhr.send(null)
5、postMessage跨域
原理
postMessage是H5原生API支持搭幻,可以在兩個(gè)頁(yè)面或者多個(gè)頁(yè)面咧擂,以及不同源頁(yè)面之間傳遞數(shù)據(jù)。窗口之間能夠傳遞數(shù)據(jù)的前提是必須從一個(gè)窗口獲取到另外一個(gè)窗口的目標(biāo)對(duì)象(target window),比如說(shuō)用iframe打開(kāi)另外一個(gè)窗口檀蹋,我們得獲取到這個(gè)iframe的contentWindow松申。或者通過(guò)window.open()打開(kāi)另外一個(gè)窗口時(shí)會(huì)返回這個(gè)窗口的window對(duì)象续扔。
參數(shù)傳遞
/**
data 需要傳遞的數(shù)據(jù)攻臀,使用JSON.stringify 序列化
origin 設(shè)置為'*'時(shí),表示傳遞給所有窗口纱昧,也可以指定地址刨啸。 如果是同源下設(shè)置為'/'
**/
postMessage(data,origin)
// 打開(kāi)一個(gè)新的窗口
var popup = window.open('http://localhost:8080');
/// 等待新窗口加載完
setTimeout(function() {
// 當(dāng)前窗口向目標(biāo)源傳數(shù)據(jù)
popup.postMessage({"age":10}, 'http://localhost:8080');
}, 1000);
// 設(shè)置監(jiān)聽(tīng),如果有數(shù)據(jù)傳過(guò)來(lái)识脆,則打印
window.addEventListener('message', function(e) {
console.log(e);
//判斷是不是目標(biāo)地址
if(e.origin !== 'http://localhost:8069')return;
// console.log(e.source === window.opener); // true
//發(fā)回?cái)?shù)據(jù)
e.source.postMessage({"age":20}, e.origin);
});
其他的解決方式
以上五種是比較常見(jiàn)的跨域解決方案设联,各有優(yōu)缺點(diǎn)善已。沒(méi)有絕對(duì)的最優(yōu)方案,只有最合適應(yīng)用場(chǎng)景离例。
常見(jiàn)的兩種代理跨域
- nginx反向代理跨域 node中間件代理
配置就不講了换团,其實(shí)我也不熟悉,平時(shí)工作用不到這么高大上的宫蛆。就來(lái)講講原理以及思路艘包。
什么是代理
既然是代理跨域,那么代理(Proxy Server)就是一個(gè)很重要的點(diǎn)耀盗,這里的代理說(shuō)的服務(wù)器代理想虎,是一種很重要的服務(wù)器安全功能,也是一種很常見(jiàn)的設(shè)計(jì)模式叛拷,來(lái)隔絕不同的模塊舌厨,解耦模塊。生活中也很容易見(jiàn)到這種模式忿薇,我們買房(買不起呀)買車裙椭,就好比跟一個(gè)大型服務(wù)器打交道,別人是大老板署浩,自然不會(huì)那么容易親自接待你揉燃,這時(shí)候有中介在中間,幫你們兩方理清好思路瑰抵,做好鋪墊你雌,然后后面的交流才會(huì)更加順通高效器联。我們?yōu)g覽器訪問(wèn)不同源的服務(wù)器是有限定的二汛,但是nginx和node中間件這些代理就沒(méi)有跨域的限定,所以我們可以放心的把任務(wù)交給他們拨拓,讓他們幫我們?nèi)プ鑫覀冏霾涣说氖隆?/p>
為什么代理是反的
我們知道單個(gè)服務(wù)器的處理能力是有限的肴颊,就好比如中國(guó)也不可能只有一家汽車生產(chǎn)商,中國(guó)那么大的市場(chǎng)渣磷,自然需要很多生產(chǎn)商才能滿足的過(guò)來(lái)婿着。那么用戶選購(gòu)的時(shí)候需要節(jié)省時(shí)間和精力,汽車之間就承擔(dān)這樣一個(gè)角色醋界,把成千上萬(wàn)的用戶需求分配出去竟宋,nginx就能夠把用戶的請(qǐng)求分發(fā)到空閑的服務(wù)器上,然后服務(wù)器返回自己的服務(wù)到負(fù)載均衡設(shè)備上形纺,然后負(fù)載均衡的設(shè)備會(huì)講服務(wù)器的服務(wù)返回給用戶丘侠,所以我們并不知道為什么服務(wù)的是哪一臺(tái)服務(wù)器發(fā)送出來(lái)的,這就很好的隱藏了服務(wù)器逐样。有一句精辟的話是這么說(shuō)的:“反向代理就是流量發(fā)散狀蜗字,代理是流量匯聚狀打肝。”
最后附上一張畫(huà)的很丑陋的圖
如果大神您想繼續(xù)探討或者學(xué)習(xí)更多知識(shí)挪捕,歡迎加入QQ或者微信一起探討:854280588QQ.png微信