何為跨域
??想必大家都知道雄坪,不在贅述了厘熟,跨域問題出現(xiàn)的原因就是瀏覽器的安全機(jī)制:同協(xié)議、域名维哈、端口绳姨。下面就總結(jié)下常用的幾種解決方案。
1阔挠、JSONP(只能發(fā)送get請求飘庄,不支持post、put购撼、delete跪削;不安全xss攻擊)
??jsonp的詳細(xì)介紹在另一篇文章,這里就以百度的查詢接口做簡單展示:
<script>
// 封裝簡單的jsonp
function jsonp(url, params, cb) {
return new Promise((resovle, reject) => {
let script = document.creatElement('script');
window[cb] = function(data) {
resovle(data);
document.body.removeChild(script);
}
params = {...params, cb}; //wd=b&cb=show
let arrs = [];
for(let key in params) {
arrs.push(`${key}=${params[key]}`);
}
script.url = `${url}?${arrs.join('&')}`;
document.body.appendChild(script);
})
}
// jsonp調(diào)用方式
jsonp({
url:'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
params:{wd: 'b'},
cb: show
}).then(data=>{
console.log(data)
})
</script>
2迂求、cors(后臺配置)
??下面以express為例
// 設(shè)置那些原可以訪問接口
res.setHeader('Access-Control-Allow-Origin', origin)
// 允許攜帶哪個頭訪問
res.setHeader('Access-Control-Allow-Headers', 'name')
// 允許那些請求方法
res.setHeader('Access-Control-Allow-Methods', 'PUT,POST,DELETE')
// 允許攜帶cookie
res.setHeader('Access-Control-Allow-Credentials',true)
// 預(yù)檢測存活時間(options請求)
res.setHeader('Access-Control-Max-Age',6000)
// 允許前端獲取哪個請求頭(允許返回的頭)
res.setHeader('Access-Control-Expose-Header','name')
3碾盐、iframe postMessage
??postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是為數(shù)不多可以跨域操作的 window 屬性之一揩局,它可用于解決以下方面的問題:
- 頁面和其打開的新窗口的數(shù)據(jù)傳遞
- 多窗口之間消息傳遞
- 頁面與嵌套的 iframe 消息傳遞
- 上面三個場景的跨域數(shù)據(jù)傳遞
postMessage()方法允許來自不同源的腳本采用異步方式進(jìn)行有限的通信毫玖,可以實(shí)現(xiàn)跨文本檔、多窗口、跨域消息傳遞付枫。
otherWindow.postMessage(message, targetOrigin, [transfer]);
- message: 將要發(fā)送到其他 window 的數(shù)據(jù)烹玉。
- targetOrigin:通過窗口的 origin 屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個 URI阐滩。在發(fā)送消息的時候二打,如果目標(biāo)窗口的協(xié)議、主機(jī)地址或端口這三者的任意一項(xiàng)不匹配 targetOrigin 提供的值掂榔,那么消息就不會被發(fā)送继效;只有三者完全匹配,消息才會被發(fā)送衅疙。
- transfer(可選):是一串和 message 同時傳遞的 Transferable 對象. 這些對象的所有權(quán)將被轉(zhuǎn)移給消息的接收方莲趣,而發(fā)送一方將不再保有所有權(quán)鸳慈。
接下來我們看個例子: http://localhost:3000/a.html頁面向http://localhost:4000/b.html傳遞“我愛你”,然后后者傳回"我不愛你"饱溢。
// a.html
<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe//等它加載完觸發(fā)一個事件
//內(nèi)嵌在http://localhost:3000/a.html
<script>
function load() {
let frame = document.getElementById('frame')
frame.contentWindow.postMessage('我愛你', 'http://localhost:4000') //發(fā)送數(shù)據(jù)
window.onmessage = function(e) { //接受返回數(shù)據(jù)
console.log(e.data) //我不愛你
}
}
</script>
// b.html
window.onmessage = function(e) {
console.log(e.data) //我愛你
e.source.postMessage('我不愛你', e.origin)
}
4、window.name
window.name 屬性的獨(dú)特之處:name 值在不同的頁面(甚至不同域名)加載后依舊存在走芋,并且可以支持非常長的 name 值(2MB)绩郎。
??a、b頁面同域翁逞,a頁面嵌套c頁面肋杖,onload事件第一次載入c頁面url變?yōu)閎頁面并且獲取contentWindow.name
// a.html(http://localhost:3000/b.html)
<iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
<script>
let first = true
// onload事件會觸發(fā)2次,第1次加載跨域頁挖函,并留存數(shù)據(jù)于window.name
function load() {
if(first){
// 第1次onload(跨域頁)成功后状植,切換到同域代理頁面
let iframe = document.getElementById('iframe');
iframe.src = 'http://localhost:3000/b.html';
first = false;
}else{
// 第2次onload(同域b.html頁)成功后,讀取同域window.name中數(shù)據(jù)
console.log(iframe.contentWindow.name);
}
}
</script>
// c.html(http://localhost:4000/c.html)
<script>
window.name = '我不愛你'
</script>
5怨喘、hash
??a中嵌套c津畸,c中嵌套b,a=>b=>c傳遞location.hash必怜,a頁面用window.onhashchange獲取hash值
// a.html
<iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
<script>
window.onhashchange = function () { //檢測hash的變化
console.log(location.hash);
}
</script>
// b.html
<script>
window.parent.parent.location.hash = location.hash
//b.html將結(jié)果放到a.html的hash值中肉拓,b.html可通過parent.parent訪問a.html頁面
</script>
// c.html
console.log(location.hash);
let iframe = document.createElement('iframe');
iframe.src = 'http://localhost:3000/b.html#idontloveyou';
document.body.appendChild(iframe);
6、document.domain
??必須是一級域名和二級域名的關(guān)系
// a.html
<body>
helloa
<iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
<script>
document.domain = 'zf1.cn'
function load() {
console.log(frame.contentWindow.a);
}
</script>
</body>
// b.html
<body>
hellob
<script>
document.domain = 'zf1.cn'
var a = 100;
</script>
</body>
7梳庆、websocket
可以參考阮大的文章暖途。
// socket.html
<script>
let socket = new WebSocket('ws://localhost:3000');
socket.onopen = function () {
socket.send('我愛你');//向服務(wù)器發(fā)送數(shù)據(jù)
}
socket.onmessage = function (e) {
console.log(e.data);//接收服務(wù)器返回的數(shù)據(jù)
}
</script>
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//記得安裝ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
ws.on('message', function (data) {
console.log(data);
ws.send('我不愛你')
});
})
8、ngxin
實(shí)現(xiàn)原理類似于 Node 中間件代理膏执,需要你搭建一個中轉(zhuǎn) nginx 服務(wù)器驻售,用于轉(zhuǎn)發(fā)請求。
使用 nginx 反向代理實(shí)現(xiàn)跨域更米,是最簡單的跨域方式欺栗。只需要修改 nginx 的配置即可解決跨域問題,支持所有瀏覽器,支持 session纸巷,不需要修改任何代碼镇草,并且不會影響服務(wù)器性能。
實(shí)現(xiàn)思路:通過 nginx 配置一個代理服務(wù)器(域名與 domain1 相同瘤旨,端口不同)做跳板機(jī)梯啤,反向代理訪問 domain2 接口,并且可以順便修改 cookie 中 domain 信息存哲,方便當(dāng)前域 cookie 寫入因宇,實(shí)現(xiàn)跨域登錄。
先下載nginx祟偷,然后將 nginx 目錄下的 nginx.conf 修改如下:
// proxy服務(wù)器
server {
listen 80;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 當(dāng)用webpack-dev-server等中間件代理接口訪問nignx時察滑,此時無瀏覽器參與,故沒有同源限制修肠,下面的跨域配置可不啟用
add_header Access-Control-Allow-Origin http://www.domain1.com; #當(dāng)前端只跨域不帶cookie時贺辰,可為*
add_header Access-Control-Allow-Credentials true;
}
}
9、webpack proxy
//服務(wù)器啟動目錄;
devServer: {
contentBase: './dist',
hot: true,
// host:'1ocalhost',
port: 8586,
// compress:true,
//解決跨域
proxy: {
'/api': {
target: 'http://localhost:8087',
pathRewrite: { '^/api': '' },
changeOrigin: true,
secure: false, // 接受 運(yùn)行在 https 上的服務(wù)
}
}
},