前言
前後端資料互動經常會碰到請求跨域,什麼是跨域,以及有哪幾種跨域方式,這是本文要探討的內容。
一、什麼是跨域?
1。什麼是同源策略及其限制內容?
同源策略是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到XSS、CSRF等攻擊。所謂同源是指“協議+域名+埠”三者相同,即便兩個不同的域名指向同一個ip地址,也非同源。
同源策略限制內容有:
Cookie、LocalStorage、IndexedDB 等儲存性內容
DOM 節點
AJAX 請求傳送後,結果被瀏覽器攔截了
但是有三個標籤是允許跨域載入資源:
2。常見跨域場景
當協議、子域名、主域名、埠號中任意一個不相同時,都算作不同域。
不同域之間相互請求資源,就算作“跨域”。常見跨域場景如下圖所示:
特別說明兩點:
第一:如果是協議和埠造成的跨域問題“前臺”是無能為力的。
第二:在跨域問題上,僅僅是透過“URL的首部”來識別而不會根據域名對應的IP地址是否相同來判斷。“URL的首部”可以理解為“協議, 域名和埠必須匹配"。
這裡你或許有個疑問:
請求跨域了,那麼請求到底發出去沒有?
跨域並不是請求發不出去,請求能發出去,服務端能收到請求並正常返回結果,只是結果被瀏覽器攔截了。
你可能會疑問明明透過表單的方式可以發起跨域請求,為什麼 Ajax 就不會?因為歸根結底,跨域是為了阻止使用者讀取到另一個域名下的內容,Ajax 可以獲取響應,瀏覽器認為這不安全,所以攔截了響應。但是表單並不會獲取新的內容,所以可以發起跨域請求。同時也說明了跨域並不能完全阻止 CSRF,因為請求畢竟是發出去了。
二、跨域解決方案
1。jsonp
1) JSONP原理
利用
標籤沒有跨域限制的漏洞,網頁可以得到從其他來源動態產生的 JSON 資料。JSONP請求一定需要對方的伺服器做支援才可以。
2) JSONP和AJAX對比
JSONP和AJAX相同,都是客戶端向伺服器端傳送請求,從伺服器端獲取資料的方式。但AJAX屬於同源策略,JSONP屬於非同源策略(跨域請求)
3) JSONP優缺點
JSONP優點是簡單相容性好,可用於解決主流瀏覽器的跨域資料訪問的問題。
缺點是僅支援get方法具有侷限性,不安全可能會遭受XSS攻擊。
4) JSONP的實現流程
宣告一個回撥函式,其函式名(如show)當做引數值,要傳遞給跨域請求資料的伺服器,函式形參為要獲取目標資料(伺服器返回的data)。
建立一個
// b。html window。onmessage = function(e) { console。log(e。data) //我愛你 e。source。postMessage(‘我不愛你’, e。origin) }
4。websocket
Websocket是HTML5的一個持久化的協議,它實現了瀏覽器與伺服器的全雙工通訊,同時也是跨域的一種解決方案。WebSocket和HTTP都是應用層協議,都基於 TCP 協議。
但是 WebSocket 是一種雙向通訊協議,在建立連線之後,WebSocket 的 server 與 client 都能主動向對方傳送或接收資料。
同時,WebSocket 在建立連線時需要藉助 HTTP 協議,連線建立好了之後 client 與 server 之間的雙向通訊就與 HTTP 無關了。
原生WebSocket API使用起來不太方便,我們使用
Socket。io
,它很好地封裝了webSocket介面,提供了更簡單、靈活的介面,也對不支援webSocket的瀏覽器提供了向下相容。
我們先來看個例子:本地檔案socket。html向
localhost:3000
發生資料和接受資料
// socket。html
// server。jslet express = require(‘express’);let app = express();let WebSocket = require(‘ws’);//記得安裝wslet wss = new WebSocket。Server({port:3000});wss。on(‘connection’,function(ws) { ws。on(‘message’, function (data) { console。log(data); ws。send(‘我不愛你’) });})
5。 Node中介軟體代理(兩次跨域)
實現原理:
同源策略是瀏覽器需要遵循的標準,而如果是伺服器向伺服器請求就無需遵循同源策略。
代理伺服器,需要做以下幾個步驟:
接受客戶端請求 。
將請求 轉發給伺服器。
拿到伺服器 響應 資料。
將 響應 轉發給客戶端。
我們先來看個例子:本地檔案index。html檔案,透過代理伺服器
http://localhost:3000
向目標伺服器
http://localhost:4000
請求資料。
// index。html(http://127。0。0。1:5500)
// server1。js 代理伺服器(http://localhost:3000)const http = require(‘http’)// 第一步:接受客戶端請求const server = http。createServer((request, response) => {// 代理伺服器,直接和瀏覽器直接互動,需要設定CORS 的首部欄位 response。writeHead(200, { ‘Access-Control-Allow-Origin’: ‘*’, ‘Access-Control-Allow-Methods’: ‘*’, ‘Access-Control-Allow-Headers’: ‘Content-Type’ }) // 第二步:將請求轉發給伺服器 const proxyRequest = http 。request( { host: ‘127。0。0。1’, port: 4000, url: ‘/’, method: request。method, headers: request。headers }, serverResponse => { // 第三步:收到伺服器的響應 var body = ‘’ serverResponse。on(‘data’, chunk => { body += chunk }) serverResponse。on(‘end’, () => { console。log(‘The data is ’ + body) // 第四步:將響應結果轉發給瀏覽器 response。end(body) }) } ) 。end()})server。listen(3000, () => {console。log(‘The proxyServer is running at http://localhost:3000’)})
// server2。js(http://localhost:4000)const http = require(‘http’)const data = { title: ‘fontend’, password: ‘123456’ }const server = http。createServer((request, response) => { if (request。url === ‘/’) { response。end(JSON。stringify(data)) }})server。listen(4000, () => { console。log(‘The server is running at http://localhost:4000’)})
上述程式碼經過兩次跨域,值得注意的是瀏覽器向代理伺服器傳送請求,也遵循同源策略,最後在index。html檔案打印出
{“title”:“fontend”,“password”:“123456”}
6。nginx反向代理
實現原理類似於Node中介軟體代理,需要你搭建一箇中轉nginx伺服器,用於轉發請求。
使用nginx反向代理實現跨域,是最簡單的跨域方式。只需要修改nginx的配置即可解決跨域問題,支援所有瀏覽器,支援session,不需要修改任何程式碼,並且不會影響伺服器效能。
實現思路:透過nginx配置一個代理伺服器(域名與domain1相同,埠不同)做跳板機,反向代理訪問domain2介面,並且可以順便修改cookie中domain資訊,方便當前域cookie寫入,實現跨域登入。
先下載nginx,然後將nginx目錄下的nginx。conf修改如下:
// proxy伺服器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; # 當用webpack-dev-server等中介軟體代理介面訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啟用 add_header Access-Control-Allow-Origin http://www。domain1。com; #當前端只跨域不帶cookie時,可為* add_header Access-Control-Allow-Credentials true; }}
最後透過命令列
nginx -s reload
啟動nginx
// index。htmlvar xhr = new XMLHttpRequest();// 前端開關:瀏覽器是否讀寫cookiexhr。withCredentials = true;// 訪問nginx中的代理伺服器xhr。open(‘get’, ‘http://www。domain1。com:81/?user=admin’, true);xhr。send();
// server。jsvar http = require(‘http’);var server = http。createServer();var qs = require(‘querystring’);server。on(‘request’, function(req, res) { var params = qs。parse(req。url。substring(2)); // 向前臺寫cookie res。writeHead(200, { ‘Set-Cookie’: ‘l=a123456;Path=/;Domain=www。domain2。com;HttpOnly’ // HttpOnly:指令碼無法讀取 }); res。write(JSON。stringify(params)); res。end(); });server。listen(‘8080’);console。log(‘Server is running at port 8080。。。’);
7。window。name + iframe
window。name屬性的獨特之處:name值在不同的頁面(甚至不同域名)載入後依舊存在,並且可以支援非常長的 name 值(2MB)。
其中a。html和b。html是同域的,都是
http://localhost:3000;
而c。html是
http://localhost:4000
// a。html(http://localhost:3000/b。html)
b。html為中間代理頁,與a。html同域,內容為空。
// c。html(http://localhost:4000/c。html)
總結:透過iframe的src屬性由外域轉向本地域,跨域資料即由iframe的window。name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。
8。location。hash + iframe
實現原理: a。html欲與c。html跨域相互通訊,透過中間頁b。html來實現。 三個頁面,不同域之間利用iframe的location。hash傳值,相同域之間直接js訪問來通訊。
具體實現步驟:一開始a。html給c。html傳一個hash值,然後c。html收到hash值後,再把hash值傳遞給b。html,最後b。html將結果放到a。html的hash值中。
同樣的,a。html和b。html是同域的,都是
http://localhost:3000;
而c。html
是http://localhost:4000
// a。html
// b。html
// c。html console。log(location。hash); let iframe = document。createElement(‘iframe’); iframe。src = ‘http://localhost:3000/b。html#idontloveyou’; document。body。appendChild(iframe);
9。document。domain + iframe
該方式只能用於二級域名相同的情況下,比如
a.test.com
和
b.test.com
適用於該方式。
只需要給頁面新增
document。domain =‘test。com’
表示二級域名都相同就可以實現跨域。
實現原理:兩個頁面都透過js強制設定document。domain為基礎主域,就實現了同域。
我們看個例子:頁面
a。zf1。cn:3000/a。html
獲取頁面
b。zf1。cn:3000/b。html
中a的值
// a。html
helloa// b。html
hellob三、總結
CORS支援所有型別的HTTP請求,是跨域HTTP請求的根本解決方案
JSONP只支援GET請求,JSONP的優勢在於支援老式瀏覽器,以及可以向不支援CORS的網站請求資料。
不管是Node中介軟體代理還是nginx反向代理,主要是透過同源策略對伺服器不加限制。
日常工作中,用得比較多的跨域方案是cors和nginx反向代理
轉自簡書:前端三少爺
原文連結:https://www。jianshu。com/p/2896288494df