摘要:跨域訪問的處理辦法及適用條件適用條件請求的接口需要支持訪問這里需要強調的是,不屬于的部分,它只是把放入標簽中實現(xiàn)的數(shù)據(jù)傳輸,不受同源策略限制。
為什么會有跨域問題
我們試想一下以下幾種情況:
我們打開了一個天貓并且登錄了自己的賬號,這時我們再打開一個天貓的商品,我們不需要再進行一次登錄就可以直接購買商品,因為這兩個網(wǎng)頁是同源的,可以共享登錄相關的 cookie 或 localStorage 數(shù)據(jù);
如果你正在用支付寶或者網(wǎng)銀,同時打開了一個不知名的網(wǎng)頁,如果這個網(wǎng)頁可以訪問你支付寶或者網(wǎng)銀頁面的信息,就會產(chǎn)生嚴重的安全的問題。如果該未知網(wǎng)站是黑客的工具,那他就可以借此發(fā)起 CSRF 攻擊了。顯然瀏覽器不允許這樣的事情發(fā)生;
想必你也有過同時登陸好幾個 qq 賬號的情況,如果同時打開各自的 qq 空間瀏覽器會有一個小號模式,也就是另外再打開一個窗口專門用來打開第二個 qq 賬號的空間。
為了解決不同域名相互訪問數(shù)據(jù)導致的不安全問題,Netscape提出的一個著名的安全策略——同源策略,它是指同一個“源頭”的數(shù)據(jù)可以自由訪問,但不同源的數(shù)據(jù)相互之間都不能訪問。
同源策略很明顯,上述第1個和第3個例子中,不同的天貓商店和 qq 空間屬于同源,可以共享登錄信息。qq 為了區(qū)別不同的 qq 的登錄信息,重新打開了一個窗口,因為瀏覽器的不同窗口是不能共享信息的。而第2個例子中的支付寶、網(wǎng)銀、不知名網(wǎng)站之間是非同源的,所以彼此之間無法訪問信息,如果你執(zhí)意想請求數(shù)據(jù),會提示異常:
No "Access-Control-Allow-Origin" header is present on the requested resource. Origin "null" is therefore not allowed access.
那么什么是同源的請求呢?同源請求要求被請求資源頁面和發(fā)出請求頁面滿足3個相同:
協(xié)議相同
host相同
端口相同
簡單理解一下:
/*以下兩個數(shù)據(jù)非同源,因為協(xié)議不同*/ http://www.abc123.com.cn/item/a.js https://www.abc123.com.cn/item/a.js /*以下兩個數(shù)據(jù)非同源,因為域名不同*/ http://www.abc123.com.cn/item/a.js http://www.abc123.com/item/a.js /*以下兩個數(shù)據(jù)非同源,因為主機名不同*/ http://www.abc123.com.cn/item/a.js http://item.abc123.com.cn/item/a.js /*以下兩個數(shù)據(jù)非同源,因為協(xié)議不同*/ http://www.abc123.com.cn/item/a.js http://www.abc123.com.cn:8080/item/a.js /* 以下兩個數(shù)據(jù)非同源,域名和 ip 視為不同源 * 這里應注意,ip和域名替換一樣不是同源的 * 假設www.abc123.com.cn解析后的 ip 是 195.155.200.134 */ http://www.abc123.com.cn/ http://195.155.200.134/ /*以下兩個數(shù)據(jù)同源*/ /* 這個是同源的*/ http://www.abc123.com.cn/source/a.html http://www.abc123.com.cn/item/b.jsHTTP 簡單請求和非簡單請求
http 請求滿足一下條件時稱為簡單請求,否則是非簡單請求:
請求方法是 HEAD,GET,POST 之一
HTTP的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
Content-Type 取值僅限于 application/x-www-form-urlencoded, multipart/form-data, text/plain
非簡單請求在發(fā)送之前會發(fā)送一次 OPTION 預請求,如果在跨域操作遇到返回 405(Method Not Allowed) 錯誤,需要服務端允許 OPTION 請求。
HTTP 跨域訪問的處理辦法及適用條件 JSOP適用條件:請求的 GET 接口需要支持 jsonp 訪問
這里需要強調的是,jsonp 不屬于 Ajax 的部分,它只是把 url 放入 script 標簽中實現(xiàn)的數(shù)據(jù)傳輸,不受同源策略限制。由于一般庫也會把它和 Ajax 封裝在一起,由于其和 Ajax 根部不是一回事,所以這里不討論。下面是一個 jsonp 的例子:
window.jsonpCallback = console.log; var JSONP = document.createElement("script"); JSONP.src = "http://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=13122222222&t=" + Math.random() + "&callback=jsonpCallback";; document.body.appendChild(JSONP);
后端支持jsonp方式(Nodejs)
var querystring = require("querystring"); var http = require("http"); var server = http.createServer(); server.on("request", function(req, res) { var params = qs.parse(req.url.split("?")[1]); var fn = params.callback; // jsonp返回設置 res.writeHead(200, { "Content-Type": "text/javascript" }); res.write(fn + "(" + JSON.stringify(params) + ")"); res.end(); }); server.listen("8080"); console.log("Server is running at port 8080...");document.domain
適用條件: host 中僅服務器不同的情況,域名本身應該相同
www.dom.com 和 w1.dom.com 需要同源才能訪問,可以將 document.domain 設置為 dom.com 解決該問題
document.domain = "dom.com";
例如,我想開發(fā)一個瀏覽器插件,發(fā)現(xiàn)騰訊視頻頁有個 iframe 其本身的跨域的,無法獲取其 iframe 的 DOM 對象。但域名部分相同,可以通過該方法解決.
注:如果你想設置它為完全不同的域名,那肯定會報同源錯誤的,注意使用范圍!
嵌入 iframe適用條件: host 中僅服務器不同的情況,域名本身應該相同
有了上面的例子就不難理解這個方法了,嚴格來說這不是一個新的方法,而是上一個方法的延伸。通過設置document.domain, 使同一個域名下不同服務器名的頁面可以訪問數(shù)據(jù),但值得注意的是:這個數(shù)據(jù)訪問不是相互的,外部頁面可以訪問 iframe 內部的數(shù)據(jù),但 iframe 無法不能訪問外部的數(shù)據(jù)。
location.hash適用條件:iframe 和其宿主頁面通信
一個完成的 url 中 # 及后面的部分為 hash, 可以通過修改這個部分完成iframe 的和宿主直接的數(shù)據(jù)傳遞,下面演示一下 iframe 頁面(B.html)像宿主(A.html)傳數(shù)據(jù), 反之同理:
// A.html data = ["book", "map", "shelf", "knife"]; setTimeout(() => { location.hash = window.encodeURIComponent(data.join("/")); }, 1000); // B.html window.parent.onhashchange = function (e) { var data = window.decodeURIComponent(e.newURL.split("#")[1]).split("/"); console.log(data); // ["book", "map", "shelf", "knife"] }
*注意反向傳遞數(shù)據(jù)時應該使用 window.parent.location.hash
window.name適用條件:宿主頁面和 iframe 之間通信
window對象有個name屬性,該屬性有個特征:即在 window 的生命周期內,窗口載入的所有的頁面 (iframe) 都是共享一個 window.name 的,每個頁面對 window.name 都有讀寫的權限,window.name 是持久存在一個窗口載入過的所有頁面中的,并不會因新頁面的載入而進行重置。
這樣在 window 中編輯 window.name 就可以在 iframe 中得到,但這個過程缺乏監(jiān)聽,宿主頁面(A.html)和 iframe 頁面(B.html)相互并不知道對方在什么時候修改該值:
// A.html setTimeout(() => { window.parent.name = "what!"; }, 2000); // B.html setTimeout(() => { console.log(window.name); // what! }, 2500);postMessage
適用條件:postMessage 是 H5 提出的一個消息互通的機制,解決 iframe 不能消息互通的問題,也可以跨 window 通信,語法如下:
// 在 www.siteA.com 中發(fā)出消息 // @message{any} 要發(fā)送的數(shù)據(jù)(注意:老版本瀏覽器只支持字符串類型) // @targetOrigin{string} 規(guī)定接收數(shù)據(jù)的域,只有其指定的域才能收到消息,如果為"*"則沒用域的限制 // transfer{any} 與 message 一同發(fā)送并轉移所有權 window.postMessage(message, targetOrigin, [transfer]); // 在另一個頁面接受參數(shù) window.onmessage = console.log;
這里暫不談論第三個參數(shù),因為你可能一輩子也用不到它。而 targetOrigin 最好不要使用 "*",除非你想讓所有頁面都收到你的消息。
一種你會用到的場景(iframe):
這一種僅僅是沒有了iframe,當你在同一個瀏覽器窗口同時打開 www.siteA.com 和 www.siteB.com 兩個標簽時也可以這樣用
反向代理服務器
頁面需要訪問一些跨域接口,由于代理的存在,在服務器看來請求是不跨域,所以使用各種請求。但需要注意 http 到 https 的兼容問題。
比如當我在一些在線平臺開發(fā)網(wǎng)站后得到一個頁面 www.site-A.com, 而這個頁面需要請求我自己的數(shù)據(jù)服務器data.site-B.com上的數(shù)據(jù), 這樣同樣會產(chǎn)生跨域問題,但是www.site-A.com這個頁面是掛在第三方服務器上的,解決這個問題可以采用代理服務器的方法:
var express = require("express"); var request = require("request"); var app = express(); app.use("/api", function(req, res) { var url = "http://data.site-B.com/api2" + req.url; req.pipe(request(url)).pipe(res); }); app.use("/", function(req, res) { var url = "http://data.site-C.com"; req.pipe(request(url)).pipe(res); });
當然還需要同時配置一個 host:
127.0.0.1 local.www.site-B.com
然后訪問 local.www.site-B.com 就 OK 了。
CORS適用條件:CORS 需要服務端支持,且存在一定的兼容性問題(如今你已經(jīng)可以不考慮,但必要時不要忘了這個"bug")。其通過添加 http 頭關鍵字實現(xiàn)跨域可訪問,包括如下頭內容:
# www.siteA.com/api 返回相應需要具有如下 http 頭字段 Access-Control-Allow-Origin: "http://www.siteB.com" # 指定域可以請求,通配符"*"(必須) Access-Control-Allow-Methods: "GET,PUT,POST,DELETE" # 指定允許的跨域請求方式(必須) Access-Control-Allow-Headers: "Content-Type" # 請求中必須包含的 http 頭字段 Access-Control-Allow-Credentials: true # 配合請求中的 withCredentials 頭進行請求驗證
通過 express 實現(xiàn)也很簡單,在注冊路由之前添加:
var cors = require("cors"); // 通過 npm 安裝 app.use(cors());
當然你也可以自定義一個中間件:
// 自定義中間件 var cors = function (req, res, next) { // 自定義設置跨域需要的響應頭。 res.header("Access-Control-Allow-Origin", "http://www.siteB.com"); res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE"); next(); }; app.use(cors); // 運用跨域的中間件WebSocket 協(xié)議跨域
ws 協(xié)議是 H5 中的 web 全雙工通信解決方案,常規(guī) http 屬于請求相應的過程,在客戶端沒有請求的情況下,服務端無法給客戶端主動推送數(shù)據(jù),ws 協(xié)議解決了這個問題,但處于安全考慮,其同樣有同源策略的限制。
*這里不討論通過長連接和服務端掛起請求等方法推送數(shù)據(jù),本文只討論跨域。
下面舉個例子(依賴socket.io.js):
// 前端部分 socket.on("connect", function() { // 監(jiān)聽服務端消息 socket.on("message", function(msg) { console.log("data from server: " + msg); }); // 監(jiān)聽服務端關閉 socket.on("disconnect", function() { console.log("Server socket has closed."); }); }); document.getElementById("input").onkeyup = function(e) { if(!e.shiftKey && !e.ctrlKey && !e.altKey && e.keyCode === 13) socket.send(this.value); }; // 后端部分(node.js) var http = require("http"); var socket = require("socket.io"); // 啟http服務 var server = http.createServer(function(req, res) { res.writeHead(200, { "Content-type": "text/html" }); res.end(); }); server.listen("8080"); console.log("Server is running at port 8080..."); // 監(jiān)聽socket連接 socket.listen(server).on("connection", function(client) { // 監(jiān)聽客戶端信息 client.on("message", function(msg) { client.send("hello:" + msg); console.log("data from client: " + msg); }); // 監(jiān)聽客戶端斷開 client.on("disconnect", function() { console.log("Client socket has closed."); }); });HTML 標簽中的 crossorigin 屬性
HTML 中 , 和 具有 crossorigin 屬性。添加屬性會使相應添加 CORS 相關 http 頭(需要服務器支持)。同時,其還有以下可能的取值:
user-credentials 該請求通過 cookie 交換 user-credentials,服務器相應需添加 Access-Control-Allow-Origin
anonymous 該請求不會通過 cookie 交換 user-credentials,服務器相應需添加 Access-Control-Allow-Credentials
當只寫了 crossorigin 屬性沒有指定值時,其默認值為 "anonymous"。即以下兩行代碼等價:
幾種不同的跨域方法比較
方法 | 使用條件 | 使用條件是否與后端交互 | 優(yōu)點 | 缺點 | |
---|---|---|---|---|---|
JSONP | 服務端支持 jsonp 請求 | 是 | 兼容所有瀏覽器 | 只支持 GET 請求,只能和服務端通信 | |
CORS | 服務器相應需要相關投資端支持 | 是 | 方便的錯誤處理,支持所有http請求類型 | 存在瀏覽器兼容性問題(如今可以忽略了) | |
document.domain | 僅需要跨子域發(fā)起請求 | 是 | 使用便捷,沒有兼容問題 | 對于完全不同的域名無法使用 | |
postMessage | 瀏覽器不同 window 間通信、 iframe 和其宿主通信 | 否 | 支持瀏覽器頁面間或頁面和 iframe 間同行 | 需要瀏覽器兼容 H5 接口 | |
window.name | iframe 和其宿主通信 | 否 | 簡單易操作 | 數(shù)據(jù)暴露在全局不安全 | |
location.hash | iframe 和其宿主通信 | 否 | 簡單易操作 | 數(shù)據(jù)在 url 中不安全并且有長度限制 | |
反向代理 | - | 是 | 任何情況都可用 | 使用比較麻煩,需要自己建立服務 |
添加 webpack 配置如下:
const config = { // ... devServer: { // ... proxy: { "/api": { target: "https://data.site-B.com/api2", changeOrigin: true, // 允許跨域 secure: false // 允許訪問 https }, "/": { target: "https://data.site-C.com", changeOrigin: true, secure: false }, } } }; module.exports = config;擴展:基于 Nginx 反向代理和CORS配置示例
CORS 配置
location / { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Credentials true; add_header Access-Control-Allow-Methods: GET,PUT,POST,DELETE; }
反向代理配置
server { listen 7001; server_name www.domain1.com; location / { proxy_pass http://www.B.com:7001; #反向代理 } }
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/98130.html
摘要:還是老規(guī)矩,從易到難吧傳統(tǒng)的定時器,異步編程等。分配對象時,先是在空間中進行分配。內存泄漏內存泄漏是指程序中己動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果。 showImg(https://segmentfault.com/img/bVbwkad?w=1286&h=876); 網(wǎng)上參差不棄的面試題,本文由淺入深,讓你在...
摘要:還是老規(guī)矩,從易到難吧傳統(tǒng)的定時器,異步編程等。分配對象時,先是在空間中進行分配。內存泄漏內存泄漏是指程序中己動態(tài)分配的堆內存由于某種原因程序未釋放或無法釋放,造成系統(tǒng)內存的浪費,導致程序運行速度減慢甚至系統(tǒng)崩潰等嚴重后果。 showImg(https://segmentfault.com/img/bVbwkad?w=1286&h=876); 網(wǎng)上參差不棄的面試題,本文由淺入深,讓你在...
摘要:負載均衡器只能與和等特定的云服務提供商一起使用,且均衡器的功能根據(jù)提供者而定。因為它是作為一個基于的控制器在內部執(zhí)行,因此對功能的訪問相對不受限制不同于外部負載均衡器,它們中的一些可能無法在層面訪問。 很多企業(yè)在部署容器的時候都會選擇Kubernetes作為其容器編排系統(tǒng)。這是對Kubernetes的可靠性,靈活性和特性廣泛的肯定。在這篇文章中,我們將對Kubernetes如何處理一個...
閱讀 857·2023-04-25 23:59
閱讀 3757·2021-10-08 10:04
閱讀 1692·2019-08-30 14:05
閱讀 1027·2019-08-30 13:58
閱讀 499·2019-08-29 18:41
閱讀 1135·2019-08-29 17:15
閱讀 2328·2019-08-29 14:13
閱讀 2753·2019-08-29 13:27