什麼是跨域? -> 靈魂問題!!!
跨域呢 就是在一個域的文件或者指令碼試圖去請求另一個域的資源。
敲鍵盤、劃重點了啊
1、首先肯定是兩個域之間的通訊
2、重點要記住指令碼這兩個字
最近的一個專案中,做一個公用的後臺的殼子(多個後臺/域名),然後呢我團隊裡面的一個小美女做的時候發現一個問題:
在這個域名下請求同一個介面
這個專案內請求沒問題,然而這個殼子中請求的卻是無許可權訪問
複製程式碼
示例圖如下
這裡我們可以看到是a.com載入的b.com下的common.js
然後通過common.js請求的api.com返回的false
所以通過一個域下的指令碼去請求另一個域的資源也是跨域
我們通常說的跨域是由瀏覽器的同源策略限制的一類請求場景
說到這裡,不得不提一下 廣義的跨域 和 同源策略
廣義的跨域
1.) 資源跳轉: A連結、重定向、表單提交
2.) 資源嵌入: <link>、<script>、<img>、<frame>等dom標籤,
還有樣式中background:url()、@font-face()等檔案外鏈
3.) 指令碼請求: js發起的ajax請求、dom和js物件的跨域操作等
複製程式碼
同源策略
含義:
A網頁設定的Cookie, B網頁不能開啟,除非這兩個網頁“同源”
目的:
為了保證使用者資訊保安,防止惡意的網站竊取資料
同源策略是指三個相同:
- 協議相同
- 域名相同
- 埠相同
同源策略的限制範圍
- Cookie、LocalStorage、和IndexDB 無法獲取
- DOM 無法獲得
- AJAX 請求不能傳送
下面說一下怎麼突破跨域
- 通過jsonp跨域
- iframe跨域
- postMessage跨域
- CORS跨域
- Nginx代理跨域
- nodejs中介軟體代理跨域
- 利用WebSocket跨域
一、通過jsonp跨域
jsonp跨域的本質是通過動態的建立script,再請求一個帶引數的網址實現跨域通訊。
- 原生實現方法示例如下:
<script>
var src = "https://xxx.com/xxx.php?a=jsonpTest&callback=onback";
var script = document.createElement("script");
script.type = 'type/javascript';
script.src = src;
document.head.appendChild(script);
// 定義回撥函式
function onBack(res) {
console.log(res);
}
<script>
複製程式碼
服務端返回:
onBack({state: true})
複製程式碼
- 通過jquery ajax實現:
$.ajax({
url: 'https://xxx.com/xxx.php',
type: 'get',
dataType: 'jsonp', // 請求方式為jsonp
jsonpCallback: "onBack", // 自定義回撥函式名
data: {a: jsonpTest},
success: function () {
console.log(res);
}
});
複製程式碼
其他框架的方法原理一致 可根據不同的request模組自行調整
後端php程式碼示例:
<?php
$callback = $_GET['callback'];
if (empty($callback)) {
echo json_encode([
state => false,
errMsg => "請求方式不正確"
]);
return;
}
$data = json_encode([
state => true,
errMsg => ""
]);
echo "{$callback}({$data})";
複製程式碼
二、iframe跨域
1. document.domain + iframe跨域
僅在主域相同,子域不同的情況下有效
複製程式碼
實現原理:兩個頁面都通過js強制設定document.domain為基礎主域,也就實現了同域。
1.) 父頁面(n.xxx.com/a.html)
<iframe id="iframe" src="http://m.xxx.com/b.html"></iframe>
<script>
document.domain = 'xxx.com';
var user = 'admin';
</script>
複製程式碼
2.) 子頁面(m.xxx.com/b.html)
<script>
document.domain = 'xxx.com';
// 獲取父視窗中變數
console.log(window.parent.user);
</script>
複製程式碼
2. location.hash + iframe跨域
實現原理:增加中間頁面來實現。三個頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js通訊。
具體實現如圖:
主要方法 onhashchange
程式碼實現:
- a.html
<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 向b.html傳hash值
setTimeout(function() {
iframe.src = iframe.src + '#user=admin';
}, 1000);
// 開放給同域c.html的回撥方法
function onCallback(res) {
console.log(res);
}
</script>
複製程式碼
- b.html
<iframe id="iframe" src="http://www.a.com/c.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
// 監聽a.html傳來的hash值,再傳給c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
複製程式碼
- c.html
<script>
// 監聽b.html傳來的hash值
window.onhashchange = function () {
// 通過操作同域a.html的js回撥,將結果傳回去
window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
};
</script>
複製程式碼
3. window.name + iframe跨域
window.name屬性的特性:
- name值在不同的頁面(甚至不同域名)載入後依舊存在
- 可以支援非常長的 name 值(2MB)
1)a.html
<script>
var proxy = function(url, callback) {
var state = 0;
var iframe = document.createElement("iframe");
iframe.src = url;
iframe.onload = function() {
if (state === 0) {
// 第一次onload成功後 切換到代理頁面
iframe.contentWindow.location = 'http://xxx.com/proxy.html';
state = 1;
} else if (state === 1) {
// 第二次onload成功後 讀取資料 並銷燬這個iframe
// 1.釋放記憶體
// 2.保證不被其他域frame js訪問
callback(iframe.contentWindow.name);
destoryFrame();
}
}
document.body.appendChild(iframe);
// 銷燬這個iframe
function destoryFrame() {
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
}
}
// 請求跨域b頁面資料
proxy('http://yyy.com/b.html', function(data){
console.log(data);
});
</script>
複製程式碼
2)b.html
<script>
window.name = '在這裡填寫跨域資料';
</script>
複製程式碼
3)proxy.html 代理頁,要求與a.html同域,內容為空即可。
通過iframe的src屬性從其他域轉向本域,跨域資料就從iframe的window.name由其他域傳遞到本域。
一波操作666,完美繞過了瀏覽器的跨域訪問限制,但是同時又是安全操作。
在阿里雲看到過相關操作
三。postMessage跨域
postMessage是HTML5 level2裡面的API,可以用來解決以下問題:
- 頁面和這個頁面開啟的新視窗的訊息傳遞
- 多個視窗之間的訊息傳遞
- 頁面與巢狀的iframe之間的訊息傳遞
postMessage(data, origin)
data:
html5規範支援任意基本型別或可複製的物件,但部分瀏覽器只支援字串,所以傳參時最好用JSON.stringify()序列化。
origin:
協議+主機+埠號,也可以設定為"*",表示可以傳遞給任意視窗,如果要指定和當前視窗同源的話設定為"/"。
示例如下:
a.html
<iframe id="iframe" src="http://yyy.com/b.html" style="display:none;"></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: '我是xxx'
};
// 向yyy.com傳送跨域資料
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://yyy.com');
};
// 接受yyy.com返回的資料
window.addEventListener('message', function(e) {
console.log(e.data);
}, false);
</script>
複製程式碼
b.html
<script>
// 接收xxx.com的資料
window.addEventListener('message', function(e) {
console.log(e.data);
var data = JSON.parse(e.data);
if (data) {
data.name2 = "我是yyyy";
// 處理後再發回xxx.com
window.parent.postMessage(JSON.stringify(data), 'http://xxx.com');
}
}, false);
</script>
複製程式碼
四、CORS跨域
普通跨域請求:只服務端設定Access-Control-Allow-Origin即可,前端無須設定,若要帶cookie請求:前後端都需要設定。
注:由於同源策略的限制,所讀取的cookie為跨域請求介面所在域的cookie,而非當前頁。
所有瀏覽器都支援該功能(IE8+:IE8/9需要使用XDomainRequest物件來支援CORS)),CORS也已經成為常用的跨域解決方案。
1.通過前端設定
// 攜帶cookie
xhr.withCredentials = true;
複製程式碼
2.服務端配置
設定header頭
// 允許跨域訪問的域名,若有埠需寫全(協議+域名+埠)
// 若沒有埠末尾不用加'/'
Access-Control-Allow-Origin *
// 允許前端帶認證cookie
// 開啟這個以後上面的不能使用萬用字元 必須指定具體域名
Access-Control-Allow-Credentials true
// 提示OPTIONS預檢的時候,後端需要設定的兩個常用頭
Access-Control-Allow-Headers Content-Type,X-Requested-With
複製程式碼
五、Nginx代理跨域
1)CORS的一種
示例:
location / {
add_header Access-Control-Allow-Origin *;
}
複製程式碼
其他配置與上述CORS類似
2)反向代理介面 通過nginx配置一個代理伺服器做跳板機,反向代理訪問介面,並修改cookie中的domain資訊,方便當前域的cookie寫入,實現跨域登入。
這波操作可以呀,老鐵
六、nodejs中介軟體代理跨域
原理和nginx相同,通過代理伺服器,實現資料的轉發,也可以通過設定cookieDomainRewrite引數修改響應頭中cookie的域名,實現當前域的cookie寫入。
七、利用WebSocket跨域
WebSocket 是H5的一種新協議,實現了瀏覽器和伺服器的全雙工通訊,同時允許跨域通訊。
通常我們會使用socket.io來簡化操作;
直接建立ws/wss連結就行了