前端跨域請求方案整理實踐
跨域請求,是前端開發比較常見的問題。通常為了提高的開發效率,項目開發過程中進行前后端分離,部署各自獨立,就可能會出現前端后域名不一致,在通訊過程中就會出現跨域的問題。由于項目開發過程中涉及,借此機會對跨域問題進行整理。

為什么會有跨域問題?
這個問題是隨著AJAX的興起,Web 應用對跨域訪問的需求就越來越多,AJAX在進行跨域請求的時候受到瀏覽器安全限制。
瀏覽器出于安全的考慮,引入了同源策略。這種策略會對頁面上執行的js訪問資源的時候進行限制,比如不能直接通過js訪問不同源之下的頁面DOM結構,同時在對不同源發送請求時也無法獲取到服務器響應內容(服務器會正常處理請求并返回響應內容,但是返回的內容被瀏覽器攔截掉了)。
什么是同源策略?
同源策略/SOP(Same origin policy)是一種約定,由Netscape公司1995年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。所謂同源是指"協議+域名+端口"三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。
同源策略將限制以下幾種行為:
- Cookie、LocalStorage 和 IndexDB 無法讀取
- DOM 和 Js對象無法獲得
- AJAX 請求不能發送
常見的跨域場景

| URL | 說明 | 是否允許通信 |
|---|---|---|
http://www.doweb.me/name1.js 、http://www.doweb.me/name2.js |
同一域名不同路徑 | 允許 |
http://www.doweb.me:8080、 http://www.doweb.me |
同一域名不同端口 | 不允許 |
http://www.doweb.me 、http://blog.doweb.me |
主域相同,子域名不同 | 不允許 |
http://www.doweb.me 、 http://www.aliyun.com |
主域不同 | 不允許 |
http://www.doweb.me 、 https://www.doweb.me |
協議不同 | 不允許 |
解決方案
jsonp跨域
通常為了減輕web服務器的負載,把js、css,img等靜態資源分離到另一臺獨立域名的服務器上,在html頁面中再通過相應的標簽從不同域名下加載靜態資源,而被瀏覽器允許,基于此原理,可以通過動態創建script,再請求一個帶參網址實現跨域通信。
前端實現代碼:
<script type="text/javascript">
function onLogin(res){
console.log(res);
}
</script>
//引用文件的方式
<script type="text/javascript" src="https://www.devpoint.com/login?user=devpoint&callback=onLogin">
//AJAX,以jquery.js
<script type="text/javascript">
$.ajax({
url:"https://www.devpoint.com/login",
type:"get",
dataType:"jsonp", //請求方式為jsonp
jsonpCallback:"onLogin", //自定義回調函數名
data:{}
});
</script>
服務端實現代碼(PHP):
echo "onLogin({"result":"success", "user": "doweb"})"
局限:僅限GET請求
document.domain + iframe 跨域
實現原理:兩個頁面通過js的document.domain強制設置為相同主域來達到同域的效果,即相當于iframe中的頁面為通信代理頁面,代理頁面必須部署在與后端服務器同源站點下。
主頁面:(假定訪問路徑為:https://blog.devpoint.cn/login.html )
<iframe id="proxyIframe" src="https://api.devpoint.cn/proxy.html"></iframe>
<script type="text/javascript">
document.domain = "devpoint.cn";
const user = "devpoint";
const elemIframe = document.getElementById("proxyIframe");
elemIframe.login(user,function(res){
console.log(res);
});
</script>
代理頁面:https://api.devpoint.cn/proxy.html
<script type="text/javascript">
document.domain = "devpoint.cn";
function ajax(data,callback){
//此處實現與真正的后端通信
}
function login(user,callback){
ajax(data,callback);
}
</script>
局限:僅限主域名一致,子域名不同的跨域。
location.hash + iframe跨域
實現原理: a欲與b跨域通信,通過中間頁c來實現。
三個頁面,不同域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通信。
具體實現:A域:a.html -> B域:b.html -> A域:c.html,a與b不同域通過hash值單向通信,b與c也不同域也只能單向通信,但c與a同域,所以c可通過parent.parent訪問a頁面所有對象。
a.html(http://www.46649.cn/a.html)
<iframe id="iframe" src="http://www.46649.cn/b.html" style="display:none;"></iframe>
<script type="text/javascript">
var iframe = document.getElementById("iframe"); //向b.html傳hash值 setTimeout(function() {
iframe.src = iframe.src + "#user=devpoint";
}, 1000);
// 開放給同域c.html的回調方法
function onCallback(res) {
alert("data from c.html ---> " + res);
}
</script>
b.html(http://www.doweb.me/b.html)
<iframe id="iframe" src="http://www.doweb.me/c.html" style="display:none;"></iframe>
<script type="text/javascript">
var iframe = document.getElementById("iframe");
//監聽a.html傳來的hash值,再傳給c.html
window.onhashchange = function () {
iframe.src = iframe.src + location.hash;
};
</script>
c.html(http://www.46649.cn/c.html)
<script type="text/javascript">
//監聽b.html傳來的hash值
window.onhashchange = function () {
//再通過操作同域a.html的js回調,將結果傳回
window.parent.parent.onCallback("hello: " + location.hash.replace("#user=", ""));
};
</script>
局限:繁瑣,且location.hash傳遞的值長度有限
postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是為數不多可以跨域操作的window屬性之一,它可用于解決以下方面的問題:
- 頁面和其打開的新窗口的數據傳遞
- 多窗口之間消息傳遞
- 頁面與嵌套的iframe消息傳遞
- 上面三個場景的跨域數據傳遞
實現原理:postMessage(data,origin)方法接受兩個參數:
data: html5規范支持任意基本類型或可復制的對象,但部分瀏覽器只支持字符串,所以傳參時最好用JSON.stringify()序列化。origin: 協議+主機+端口號,也可以設置為"*",表示可以傳遞給任意窗口,如果要指定和當前窗口同源的話設置為"/"。
CORS跨域
普通跨域請求:服務端設置Access-Control-Allow-Origin即可,前端無須設置;
跨域請求要帶cookie:前后端都需要設置。
需注意的是:由于同源策略的限制,所讀取的cookie為跨域請求接口所在域的cookie,而非當前頁。如果想實現當前頁cookie的寫入。
目前,所有瀏覽器都支持該功能,CORS也已經成為主流的跨域解決方案。在項目中的DEBUG功能的跨域請求就是使用這個方案。
前端設置:需要在請求頭中設置withCredentials屬性
headers: {
"x-fdn-sign": apiSign,
withCredentials: "true"
}
服務端設置
response.setHeader("Access-Control-Allow-Origin", "*"); // 若有端口需寫全(協議+域名+端口)
response.setHeader("Access-Control-Allow-Credentials", "true");