走过路过来看看各种实现跨域的方式~
跨域是什么?
說到跨域我們先來說說同源,同源是指"協(xié)議+域名+端口"三者相同,所謂的跨域就是跨協(xié)議、跨域名、跨端口。
跨域?qū)е碌膯栴}
由于安全原因,跨域訪問是被各大瀏覽器所禁止的。
Cookie、LocalStorage 和 IndexDB 無法跨域讀取
假如你登錄了某個銀行網(wǎng)站那么該網(wǎng)站會發(fā)送cookie作為登錄憑證。假如此時你訪問了惡意網(wǎng)站,對其發(fā)送請求如果可以攜帶cookie跨域,那么cookie會發(fā)送到惡意網(wǎng)站,此時就可以模擬用戶去銀行網(wǎng)站發(fā)送請求。
DOM同源策略也一樣,不能通過iframe引入其他域下的頁面直接操作其dom元素
在自己的網(wǎng)站嵌入別人的網(wǎng)站,如果嵌入的網(wǎng)站有登錄功能,如果可以跨域獲得嵌入頁面的元素,那在我們的網(wǎng)站上可以輕松獲取到用戶的賬號密碼。
ajax請求不能跨域發(fā)送
如果不限制ajax跨域發(fā)送數(shù)據(jù),就相當于網(wǎng)站的所有接口對所有人都是公開的
跨域解決方案
一.jsonp
jsonp主要是利用標簽的src屬性沒有受同源策略限制來獲取來自其他域名的數(shù)據(jù)。我們通常使用 img/script 標簽。
先舉個例子~ (bd搜索框) 在訪問bd進行搜索時我們可以很輕松的獲取搜索接口
https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list
接口返回的結(jié)果
// 不難發(fā)現(xiàn)cb傳什么返回的就是什么 list({q:"hello",p:false,s:["hello kitty","hello kitty樂園","hello 樹先生","hello world","hellobike","hello tv","hello女神","hellotalk","hello語音","hello venus"]}); 復制代碼開始來寫我們的例子
<script>function list(data){console.log(data);} </script> <script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=hello&cb=list"></script> 復制代碼好了這就已經(jīng)實現(xiàn)了跨域了,我們訪問了bd的接口!
加深點印象我們自己來寫下服務端吧!
// client <script>function list(data){console.log(data);} </script> <script src="http://localhost:3000/getData?cb=list"></script> // server let express = require('express'); let app = express(); app.get('/getData', (req,res) => {let cbName = req.query.cbres.end(`${cbName}({name:'jw'})`); }); app.listen(3000); 復制代碼缺點:它只支持GET請求而不支持POST等其它類型的HTTP請求,可能會導致xss攻擊
二.CORS
CORS(Cross-Origin Resource Sharing, 跨源資源共享)是W3C出的一個標準,想要實現(xiàn)跨域主要靠服務器進行一些設置??蛻舳瞬挥米鋈魏胃?#xff01;
先列點雜七雜八的東西,后面我們會用到!
Access-Control-Allow-Origin 允許的域
Access-Control-Allow-Credentials 允許攜帶cookie
Access-Control-Expose-Headers 允許客戶端獲取哪個頭
Access-Control-Allow-Methods 允許的方法
Access-Control-Allow-Headers 允許哪些頭
Access-Control-Max-Age 預檢請求的結(jié)果緩存
廢話不多說先上例子~啟動兩個服務
// 通過3000端口來啟動index.html let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.listen(3000);// 4000端口有g(shù)etData接口 let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.get('/getData', (req,res) => {console.log(req.headers);res.end('{name:"jw"}') }); app.listen(4000); 復制代碼在html發(fā)送ajax請求
let xhr = new XMLHttpRequest(); xhr.open('get','/getData',true); xhr.onreadystatechange = function () {if(xhr.readyState === 4){if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){console.log(xhr.response)}} } xhr.send(); 復制代碼 我們可以發(fā)現(xiàn)雖然沒有獲取到數(shù)據(jù)但是4000端口其實是接收到了響應的,只是瀏覽器默認屏蔽了響應結(jié)果第一次改造服務端:
當請求發(fā)送過來時,要根據(jù)當前客戶端的origin屬性判斷是否允許跨域
let express = require('express'); let app = express(); app.use(express.static(__dirname)); let whiteList = ['http://localhost:3000']; app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {// 設置某個域可以允許訪問,也可以使用* 但是不建議這樣做res.setHeader('Access-Control-Allow-Origin', req.headers.origin)}next(); }) app.get('/getData', (req,res) => {console.log(req.headers);res.end('{name:"jw"}'); }); app.listen(4000); 復制代碼默認跨域是不攜帶cookie的,可是我想帶!強制設置攜帶cookie
// client document.cookie = 'a=1' let xhr = new XMLHttpRequest(); xhr.withCredentials = true; 復制代碼第二次改造服務端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin); + res.setHeader('Access-Control-Allow-Credentials',true)}next(); }) 復制代碼客戶端還是不滿意想在傳遞些自定義的頭!
document.cookie = 'a=1' let xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.open('get','http://localhost:4000/getData',true); xhr.setRequestHeader('name', 'jw'); 復制代碼第三次改造服務端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin);res.setHeader('Access-Control-Allow-Credentials',true); + res.setHeader('Access-Control-Allow-Headers',"name");}next(); }); 復制代碼客戶端又發(fā)現(xiàn)發(fā)送put請求時。又出現(xiàn)了惡心的問題......
xhr.open('put','http://localhost:4000/getData',true); 復制代碼 這回我一眼就看到了問題,肯定是沒有設置允許接收的方法第四次改造服務端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin);res.setHeader('Access-Control-Allow-Credentials',true);res.setHeader('Access-Control-Allow-Headers',"name"); + res.setHeader('Access-Control-Allow-Methods','PUT');}next(); }); app.put('/getData', (req,res) => {console.log(req.headers);res.end('{name:"jw"}'); }); 復制代碼等等怎么多了個請求?
原來是這樣對于非簡單請求,會在正式通信之前,增加一次HTTP查詢請求,稱為"預檢"請求(preflight)。類型為 options。
第五次改造服務端:
app.use(function (req,res,next) {if (whiteList.includes(req.headers.origin)) {res.setHeader('Access-Control-Allow-Origin', req.headers.origin);res.setHeader('Access-Control-Allow-Credentials',true);res.setHeader('Access-Control-Allow-Headers',"name");res.setHeader('Access-Control-Allow-Methods','PUT');// 設置預檢查發(fā)送的時間 + res.setHeader('Access-Control-Max-Age',6000);if (req.method === 'OPTIONS'){res.end();}else{next();}} }); 復制代碼到此客戶端終于不搞事了。服務端為了表示友好給他寫了個頭
app.put('/getData', (req,res) => {res.setHeader('info','ok');res.end('{name:"jw"}'); }); 復制代碼xhr.onreadystatechange = function () {if(xhr.readyState === 4){if(xhr.status >=200 && xhr.status<300|| xhr.status ===304){ + console.log(xhr.getResponseHeader('info'));console.log(xhr.response);}} } 復制代碼這才發(fā)現(xiàn)客戶端原來是收不到的,好吧!還需要告訴客戶端可以拿到這個值
app.put('/getData', (req,res) => {res.setHeader('info', 'ok');res.setHeader('Access-Control-Expose-Headers', 'info');res.end('{name:"jw"}'); }); 復制代碼到此! 終于知道cors是個什么鬼了,就是設置各種約定好的頭
CORS 不僅使用方便,支持所有類型請求,具有權(quán)限控制,而且瀏覽器原生支持,我們可以輕易的處理請求異常。利于排查錯誤。所以我們大多數(shù)情況下會首選該方式。在低版本瀏覽器可以使用jsonp配合其他方式來兼容。
三.postMessage
在來看看兩個窗口是如何實現(xiàn)跨域的,先看看H5的postMessage! postMessage()方法可以安全地實現(xiàn)跨源通信
現(xiàn)在的需求是這樣的 http://127.0.0.1:3000/a.html 想對http://127.0.0.1:4000/b.html 說我愛你,之后b頁面收到消息后對其說我不愛你~,這事多么悲傷的一個故事#_#。
// a.html <iframe src="http://localhost:4000/b.html" id="frame" onload="load()" frameborder="0"></iframe> <script>function load() {let frame = document.getElementById('frame');frame.contentWindow.postMessage('我愛你','http://localhost:4000');window.onmessage = function (e) {console.log(e.data);}} </script>// b.html <script>window.onmessage = function (e) {console.log(e.data);e.source.postMessage('我不愛你',e.origin);} </script> 復制代碼四.document.domain
domain方式跨域要先保證兩個頁面屬于同一個域下的 我們在hosts文件增加兩個映射關(guān)系,位置在 C:\Windows\System32\drivers\etc
127.0.0.1 a.fullstackjavascript.cn 127.0.0.1 b.fullstackjavascript.cn 復制代碼// a.html <iframe src="http://b.fullstackjavascript.cn:4000/b.html" frameborder="0" id="frame"onload="load()" ></iframe> <script>function load() {document.domain = 'fullstackjavascript.cn';console.log(frame.contentWindow.name);} </script>// b.html <script>document.domain = 'fullstackjavascript.cn';var name = 'jw'; </script> 復制代碼訪問http://a.fullstackjavascript.cn:3000/a.html,發(fā)現(xiàn)是可以拿到另一個頁面中的值
五.location.hash
用hash傳遞數(shù)據(jù)就比較有意思了,需要借助第三個頁面實現(xiàn)。
先用粗俗的話介紹一下,先有三個頁面hash1,hash2,hash3他們之間的關(guān)系是hash1和hash2是同域下的hash3是獨立域下的,我們通過hash1頁面用iframe引入hash3頁面,可以在引入時給hash3頁面?zhèn)鬟fhash值,hash3接到hash值后算出需要返回結(jié)果,在創(chuàng)建iframe引入hash2把結(jié)果通過hash的方式傳遞給hash2,hash2和hash1是同域的,hash2可以直接操控hash1的值,此時hash1頁面可以監(jiān)控hash值的變化來獲取hash2的數(shù)據(jù)
效果是沒問題的,不過感覺有點麻煩~
六.window.name
使用window.name跨域是利用切換路徑后window上的name屬性是會保留的。不懂?沒關(guān)系,舉個例子,還是有三個頁面a,b,c。a和b是同域下的,c自己一個域。a先引用c,c將想表達的內(nèi)容放到name,屬性上之后,a改變引用路徑,改成引用b,此時name屬性不會被刪除,因為a,b是同域的,所以可以直接獲取。
// a.html <iframe src="http://a.fullstackjavascript.cn:4000/c.html" id="myFrame" onload="load()" frameborder="0"></iframe> <script>let first = truefunction load() {if(first){myFrame.src = 'http://b.fullstackjavascript.cn:3000/c.html';first = false}else{let name = myFrame.contentWindow.name;console.log(name);}} </script> // c.html <script>window.name = '我愛你' </script> 復制代碼直接訪問http://b.fullstackjavascript.cn:3000/a.html發(fā)現(xiàn)可以拿到我們想要的結(jié)果啦~
七.WebSocket
WebSocket最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發(fā)送信息,是真正的雙向平等對話。這個不是咱們考慮的問題。咱們現(xiàn)在的話題是跨域,恰恰WebSocket是沒有同源限制的!
可以直接創(chuàng)建socket對象和服務器通信發(fā)消息
// socket.html <script>let socket = new WebSocket('ws://localhost:4000');socket.onopen= function () {socket.send('我愛你');}socket.onmessage = function (e) {console.log(e.data);} </script> 復制代碼服務端需要ws模塊
$ npm install ws 復制代碼let express = require('express'); let app = express(); let WebSockect = require('ws'); let wss = new WebSockect.Server({port:4000}); wss.on('connection',function (ws) {ws.on('message',function (data) {console.log(data);ws.send('我不愛你');}); }) 復制代碼我們都知道好東西往往不兼容,我們可以使用socket.io模塊保證兼容性問題。這里我就不過多去介紹websocket的使用了
八.Proxy
我們來看看服務端是如何實現(xiàn)代理的,也就是我們常說的反向代理!大家可能都對webpack比較熟悉了,webpack-dev-server內(nèi)置了http-proxy-middleware。那好吧咱們就用express配合這個中間件插件來試試這東西怎么玩。
// 3000服務器 let express = require('express'); let app = express(); var proxy = require('http-proxy-middleware'); let proxyOptions = {target: 'http://localhost:4000',changeOrigin: true }; app.use('/api', proxy(proxyOptions)); app.listen(3000); 復制代碼// 4000服務器 let express = require('express'); let app = express(); app.get('/api', (req,res) => {res.end('ok'); }) app.listen(4000); 復制代碼當我們訪問http://localhost:3000/api路徑時請求會被轉(zhuǎn)發(fā)到4000服務器上,將4000服務器上的結(jié)果響應給客戶端
九.Nginx
我相信大家對Nginx都并不陌生,他可以實現(xiàn)代理我們的接口。這里我就簡單的使用一下展示下效果! 這里以windows系統(tǒng)為例:
先要安裝nginx 下載地址
更改conf/nginx.conf增加訪問代理
location /api/ {proxy_pass http://localhost:4000; } 復制代碼雙擊nginx.exe運行,訪問localhost/api發(fā)現(xiàn)返回了4000服務器上的結(jié)果。
廢了這么多口水,終于把前端常用的跨域手段介紹了一遍!有什么疑問歡迎聯(lián)系我!
結(jié)語
喜歡的點個贊吧^_^!
支持我的可以給我打賞哈!
總結(jié)
以上是生活随笔為你收集整理的走过路过来看看各种实现跨域的方式~的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 局域网物理机与虚拟机的互通访问
- 下一篇: kafka comsumer