1.跨域的相關概念
1.1 什麼是跨域
同源策略是由 Netscape 提出的著名安全策略,是瀏覽器最核心、基本的安全功能,它限制了一個源(origin)中載入文字或者指令碼與來自其他源(origin)中資源的互動方式,所謂的同源就是指協議、域名、埠相同。
當瀏覽器執行一個指令碼時會檢查是否同源,只有同源的指令碼才會執行,如果不同源即為跨域。
域名組成:
1.2 為什麼會出現跨域
瀏覽器的同源策略,出於防範跨站指令碼的攻擊,禁止客戶端指令碼(如 JavaScript)對不同域的服務進行跨站呼叫(通常指使用 XMLHttpRequest 請求)。
1.3 瀏覽器的同源策略:Same-Origin Policy
- 沒有同源策略的危害
防止惡行請求
案例講解:
假設有一個駭客叫做小黑,他從網上抓取了一堆美女圖做了一個網站,每日訪問量爆表。
為了維護網站執行,小黑掛了一張收款碼,覺得網站不錯的可以適當資助一點,可是無奈伸手黨太多,小黑的網站入不敷出。
於是他非常生氣的在網頁中寫了一段js程式碼,使用ajax向淘寶發起登陸請求,因為很多數人都訪問過淘寶,所以電腦中存有淘寶的cookie,不需要輸入賬號密碼直接就自動登入了,然後小黑在ajax回撥函式中解析了淘寶返回的資料,得到了很多人的隱私資訊,轉手一賣,小黑的網站終於盈利了。
如果跨域也可以傳送AJAX請求的話,小黑就真的獲取到了使用者的隱私併成功獲利了!!!
1.4 跨域有哪些限制
- Cookie、LocalStorage 和 IndexDB 無法讀取。
- DOM 無法獲得。:釣魚網站
- AJAX 請求不能傳送。
1.5 Ajax請求步驟:
(1)建立XMLHttpRequest 物件。
(2)使用open方法設定請求的引數。open(method, url, 是否非同步)。
(3)傳送請求。
(4)註冊事件。 註冊onreadystatechange事件,狀態改變時就會呼叫。
(5)獲取返回的資料,更新UI。
2.JSONP(JSON with Padding)
2.1 概念
原理就是利用了 script 標籤不受同源策略的限制,在頁面中動態插入了 script,script 標籤的 src 屬性就是後端 api 介面的地址,並且以 get 的方式將前端回撥處理函式名稱告訴後端,後端在響應請求時會將回撥返還,並且將資料以引數的形式傳遞回去。
2.2 核心程式碼
前端:
var script = document.createElement('script');
script.src = 'http://127.0.0.1:2333/jsonpHandler?callback=_callback';
document.body.appendChild(script);
var _callback = function(obj) {
for(key in obj) {
console.log('key: ' + key +' value: ' + obj[key]);
}
}
後端:
[HttpGet]
[Route("jsonpForVue")]
public void GetjsonpForVue(string callback)
{
TokenModelJwt tokenModelJwt=new TokenModelJwt(){
Role="admin",
Uid=1,
Work="dsdf"
};
//string call = "({" + response + "})";
string response = string.Format("\"name\":\"{0}\"", "zhagnsan");
var modlestr = JsonConvert.SerializeObject(tokenModelJwt);
string call = callback + "(" + modlestr + ")";
Response.WriteAsync(call);
}
2.3 優劣
劣:
- 這種方式只能發生get請求;
- 確定jsonp的請求是否失敗並不容易,大多數框架的實現都是結合超時時間來判定;
- 不太安全,可能也會受到攻擊;
優:
- 很簡單
- 老式瀏覽器全部支援,伺服器改造非常小
3 Proxy代理
3.1 概念
- vue-cli的proxyTable用的是http-proxy-middleware中介軟體
- create-react-app用的是webpack-dev-server內部也是用的http-proxy-middleware
- http-proxy-middleware內部用的http-proxy
跨域是瀏覽器禁止的,服務端並不禁止跨域
所以瀏覽器可以發給自己的服務端然後,由自己的服務端再轉發給要跨域的服務端,做一層代理。
3.2 核心程式碼
前端:
proxy: {
// 配置多個代理
"/api": {
target: "http://localhost:5000",//這裡改成你自己的後端api埠地址,記得每次修改,都需要重新build
//target: "http://localhost:58427",
//target: "http://api.douban.com",
ws: true,
changeOrigin: true,
pathRewrite: {
// 路徑重寫,
"^/apb": "" // 替換target中的請求地址
}
}
3.3 優劣
缺點:不能用在生產環境,只能在開發環境;
優點:dev環境配置很簡單,支援多個域名;
4 CORS,全稱:Cross-Origin Resource Sharing(跨域資源共享)
4.1 概念
CORS是一種允許當前域(origin)的資源被其他域(origin)的指令碼請求訪問的機制。
當使用 XMLHttpRequest 傳送請求時,瀏覽器如果發現違反了同源策略就會自動加上一個請求頭 origin,後端在接受到請求後確定響應後會在 Response Headers 中加入一個屬性 Access-Control-Allow-Origin,值就是發起請求的源地址(http://127.0.0.1:8888),瀏覽器得到響應會進行判斷 Access-Control-Allow-Origin 的值是否和當前的地址相同,只有匹配成功後才進行響應處理。
簡單請求
- GET
- HEAD
- POST
- 條件 2:Content-Type 的值僅限於下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
複雜請求
4.2 核心程式碼
後端配置
4.3 優劣
缺點:
- 部分老的瀏覽器不支援
優點:
- 支援所有 Http 謂詞請求
- 不用注意介面規則
- 可用在生產環境
5 Nginx
概念:nginx解決跨域 是利用其反向代理的能力
5.1 跨域原理
Brower =》 host =》 nginx =》 目標地址伺服器資料 =》 nginx =》 Brower
也就是說,nginx並不是透過監聽brower的請求。而是作為一個伺服器,接收外部對本機的請求。所以是先透過host,讓請求指向本機,才會經過nginx。才能進行轉發。
- 首先,直接在瀏覽器位址列中,輸入某介面地址。是不會產生跨域問題的。
- 只有當在某域名的頁面中,由該頁面發起的介面請求。才可能會跨域。
- nginx就類似於這個瀏覽器位址列,它接收到外部對它的請求( 注意,nginx只會接收別人對它的請求,而不會攔截瀏覽器的請求 ),再類似瀏覽器位址列一樣去請求某個介面。最後將請求到的內容返回回去
5.2 核心程式碼
server {
listen 1005;
server_name localhost;
location / {
root nuxt\Blog.Admin\dist;
index index.html index.htm;
}
location /api {
rewrite ^.+apb/?(.*)$ /$1 break;
include uwsgi_params;
proxy_pass http://localhost:1004; #// 這是 netcore 埠
}
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
5.3 優劣
缺點:
- 移植靈活性 低、每套環境配置可能均不相同
優點:
- 對獨立性強的小專案,使用nginx則可以降低你的開發成本,快速發開快速上線
6 Socket
WebSocket是一種通訊協議,使用ws://(非加密)和wss://(加密)作為協議字首。該協議不實行同源政策,只要伺服器支援,就可以透過它進行跨源通訊。
核心程式碼:
var ws = new WebSocket('wss://echo.websocket.org'); //建立WebSocket的物件。引數可以是 ws 或 wss,後者表示加密。
//把請求發出去
ws.onopen = function (evt) {
console.log('Connection open ...');
ws.send('Hello WebSockets!');
};
//對方發訊息過來時,我接收
ws.onmessage = function (evt) {
console.log('Received Message: ', evt.data);
ws.close();
};
//關閉連線
ws.onclose = function (evt) {
console.log('Connection closed.');
};