搞懂:前端跨域問題JS解決跨域問題VUE代理解決跨域問題原理
阿新 • • 發佈:2020-05-19
## 什麼是跨域
*跨域*:`一個域下的文件或指令碼試圖去請求另一個域下的資源`
*廣義的跨域包含一下內容*:
1.資源跳轉(連結跳轉,重定向跳轉,表單提交)
2.資源請求(內部的引用,指令碼script,圖片img,frame)
3.script內部發起的請求(ajax,dom請求,和js跨域呼叫
*跨域問題出現*:
只有瀏覽器端出現,直接用終端請求,是不會出現跨域攔截,是屬於瀏覽器端的安全策略,瀏覽器將不同源的請求進行了攔截,限制了跨域資源訪問
*什麼是同源*
同源策略:same origin policy,如果兩個資源(頁面)`協議`,`域名`,`埠`都相同,那麼就是同源。
即使:兩個不同域名指向同一個ip,也算非同源
*非同源(跨域)有哪些限制*
* cookie,localstorage,indexDB無法讀取
* Dom和JS物件無法互通
* Ajax請求不能傳送
*常見的跨域demo*
```javascript
URL 說明 是否允許通訊
http://www.domain.com/a.js
http://www.domain.com/b.js 同一域名,不同檔案或路徑 允許
http://www.domain.com/lab/c.js
http://www.domain.com:8000/a.js
http://www.domain.com/b.js 同一域名,不同埠 不允許
http://www.domain.com/a.js
https://www.domain.com/b.js 同一域名,不同協議 不允許
http://www.domain.com/a.js
http://192.168.4.12/b.js 域名和域名對應相同ip 不允許
http://www.domain.com/a.js
http://x.domain.com/b.js 主域相同,子域不同 不允許
http://domain.com/c.js
http://www.domain1.com/a.js
http://www.domain2.com/b.js 不同域名 不允許
```
## 前端解決跨域的方法
* JSONP
* 原理:由於在一個頁面內部js如果要訪問另一個非同源域的資料是被瀏覽器限制的,但是瀏覽器在解析和載入script標籤的時候,是允許一個頁面載入多個域的js標籤,而js標籤其實又是類似一種get請求的方式,只是返回的資料是一個JSON格式的指令碼物件。
* 用法:將get請求封裝到script標籤中,利用script標籤做get請求,最後解析script標籤資料
* 程式碼:
```javascript
var script = document.createElement('script')
script.style = 'text/javascript'
//將要請求的get地址傳到script的src屬性,並且添加回調函式
script.setAttribute('scr','http://www.domain2.com:8080/login?user=admin&callback=handleCallback')
//將script標籤放入文件
document.head.appendChild(script)
//執行回撥函式
function handleCallback(res){
//處理JSON格式資料
alert(JSON.stringify(res))
}
```
* 由於請求script只能是get方法,所以JSONP這種方式只能解決get請求,post或者其他http方法無法解決
* 跨域資源共享
* 原理:
* cors:cross-origin -resouce sharing跨域資源共享,允許瀏覽器向跨源伺服器發出http請求,需要瀏覽器和伺服器同時支援
* 瀏覽器在發出跨域請求時:1。簡單請求,直接在頭部資訊中帶上origin資訊,標識屬於哪個源,服務端配置`access-control-allow-origin`接受哪些域名跨域訪問,可以是`*`允許所有
* 如果是非簡單請求會在正式請求之前,傳送預檢請求
* 簡單來說(簡單請求):
* 是一個雙端配合,允許打卡跨域資源限制的一種手段
* 瀏覽器端:請求頭攜帶origin資訊
* 服務端:配置`access-control-allow-origin`允許哪些源跨域
* 程式碼:
* 原生:`xmlHttpRequest`不需要配置請求頭,簡單請求會自動帶上origin屬性
```javascript
var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest相容
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
};
```
* AJAX:有一個屬性`crossDomain`,配置為true之後會讓請求頭攜帶orgin跨域額外資訊,但是不會自動包含cookie
```javascript
$.ajax({
...
xhrFields: {
withCredentials: true // 前端設定是否帶cookie
},
crossDomain: true, // 會讓請求頭中包含跨域的額外資訊,但不會含cookie
...
});
```
* VUE中的axios,預設也會把origin放到請求頭,不需要額外配置
* 服務端配置
```javascript
/*
* 匯入包:import javax.servlet.http.HttpServletResponse;
* 介面引數中定義:HttpServletResponse response
*/
// 允許跨域訪問的域名:若有埠需寫全(協議+域名+埠),若沒有埠末尾不用加'/'
response.setHeader("Access-Control-Allow-Origin", "http://www.domain1.com");
// 允許前端帶認證cookie:啟用此項後,上面的域名不能為'*',必須指定具體的域名,否則瀏覽器會提示
response.setHeader("Access-Control-Allow-Credentials", "true");
// 提示OPTIONS預檢時,後端需要設定的兩個常用自定義頭
response.setHeader("Access-Control-Allow-Headers", "Content-Type,X-Requested-With");
```
* 反向代理(NodeJs中介軟體代理跨域)(Vue代理跨域)(nginx轉發代理)
* 場景:當伺服器端無法修改cors,並且client全部換成jsonp方式比較繁瑣時,只能通過中間代理伺服器,將代理伺服器設定為支援CORS或者代理伺服器和請求頁面同源,使頁面可以直接請求代理伺服器,在通過代理伺服器進行介面代理,代理請求目標介面地址,返回資料
* 原理:跨域限制僅是瀏覽器端的安全策略,並不是http協議的固有限制,所以中間伺服器在引數和cookie有效的情況下是可以正常的請求目標伺服器的,Vue(`node+webpack+webpack-dev-server`)中配置`proxy`就是啟動一個同源的服務進行介面代理
* 注意:
* 1.非vue ,webpack,dev-serve服務,單獨啟的nginx或者node express服務做反向代理時,一般是跟頁面非同源,非同源要訪問代理伺服器也存在跨域問題,需要配置`cors`允許跨域訪問,配置`access-control-allow-header`
* 在webpack-dev-server服務,預設應該是基本與測試環境頁面同源,不需要配置請求頭允許
* 程式碼:
* webpack-dev-server代理
```javascript
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.domain2.com:8080', // 代理跨域目標介面
changeOrigin: true,
secure: false, // 當代理某些https服務報錯時用
cookieDomainRewrite: 'www.domain1.com' // 可以為false,表示不修改
}],
noInfo: true
}
}
```
* node express反向代理
```javascript
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/', proxy({
// 代理跨域目標介面
target: 'http://www.domain2.com:8080',
changeOrigin: true,
// 修改響應頭資訊,實現跨域並允許帶cookie
onProxyRes: function(proxyRes, req, res) {
res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
res.header('Access-Control-Allow-Credentials', 'true');
},
// 修改響應資訊中的cookie域名
cookieDomainRewrite: 'www.domain1.com' // 可以為false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
```
* nginx轉發代理
```shell
#proxy伺服器
server {
listen 81;
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;
# 當用webpack-dev-server等中介軟體代理介面訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啟用
add_header Access-Control-Allow-Origin http://www.domain1.com; #當前端只跨域不帶cookie時,可為*
add_header Access-Control-Allow-Credentials true;
}
}
```
* 其他js HACK方法
* 主域相同子域不同場景:docment.domain+iframe
* 三個頁面跨域互傳 : location.hash + iframe
* window.name + iframe
* H5新增的postMessage(跨視窗訊息互通方法)
* 關於iframe:
* 其實相當於在當前頁面下新開了個子頁面。遵循一切頁面與頁面之間資源訪問原則,例如
* 1.當主頁面和iframe頁面不同源時,無法互相訪問DOM
* 2.當主域一樣,二級域名不同時,document.domain的設定,可以規避同源策略,可以互相訪問DOM
* location.hash:
* url 末尾`#`後面跟隨的時頁面片段識別符號,用來表示瀏覽器渲染哪部分頁面資訊,但是改變這個值頁面不會重新重新整理, 並且設定之後,所有頁面可以通過document物件訪問該視窗的location 資訊進行獲取,可以實現巧妙跨域
* 程式碼:
```javascript
//父視窗可以把資訊,寫入子視窗的片段識別符號。
var src = originURL + ‘#’ + data;
document.getElementById(‘myIFrame’).src = src;
//子視窗通過監聽hashchange事件得到通知。
window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// …
}
```
* 同理,window.name也是一種hack方法實現跨域的方式
* 瀏覽器視窗有window.name屬性。這個屬性的最大特點是,無論是否同源,只要在同一個窗口裡,前一個網頁設定了這個屬性,後一個網頁可以讀取它。
父視窗先開啟一個子視窗,載入一個不同源的網頁,該網頁將資訊寫入window.name屬性。
window.name = data;
接著,子視窗跳回一個與主視窗同域的網址。
location = ‘http://parent.url.com/xxx.html’;
然後,主視窗就可以讀取子視窗的window.name了。
var data = document.getElementById(‘myFrame’).contentWindow.name;
這種方法的優點是,window.name容量很大,可以放置非常長的字串;缺點是必須監聽子視窗window.name屬性的變化,影響網頁效能。
* postMessage
* window.name和location.hash都屬於取巧的方式,利用了瀏覽器頁面的非資料屬性或者是視窗的固有屬性達到跨頁面的目的,但是在H5新增了postMessage API,實現跨tab跨視窗的資料通訊,允許視窗通訊,並且不論是否同源
----
## 總結:
常用的,還是代理,還有CORS,或者是JSONP這三種方式,禁止跨域本來就是瀏覽器的一種安全策略,雖然有一定限制開發者的手腳,但是在一些特殊的網站或者安全性要求比較高的網站,網路安全還是不可忽視的
----
非常感謝:下面的文章給了我很多的幫助,感謝各位前行者的辛苦付出,可以點選查閱更多資訊
[前端常見解決跨域問題方案](https://segmentfault.com/a/1190000011145364)
[9種常見的跨域解決方案](http://www.imooc.com/article/291931)
[前端跨域問題](https://segmentfault.com/a/119000000