前端跨域解決方案

跨域請(qǐng)求

全稱(chēng):非同源策略請(qǐng)求(同源即同協(xié)議、同域名和同端口),而ajax專(zhuān)門(mén)用于處理同源策略請(qǐng)求,因此對(duì)于非同源請(qǐng)求,特別是在前后端分離的項(xiàng)目當(dāng)中,我們就會(huì)面臨這類(lèi)問(wèn)題。
這里要聲明一下:跨域是瀏覽器行為,而不是服務(wù)器行為(當(dāng)前臺(tái)向服務(wù)器發(fā)起請(qǐng)求時(shí),后臺(tái)并沒(méi)有阻止請(qǐng)求操作,但后臺(tái)返回?cái)?shù)據(jù)時(shí),瀏覽器基于安全考慮,若非同源請(qǐng)求,將可能禁止對(duì)請(qǐng)求數(shù)據(jù)的處理)

常用跨域解決方案

基于Jsonp

我們可以發(fā)現(xiàn)在html當(dāng)中含有src屬性的標(biāo)簽,如<srcipt>/<img>/<link>/<iframe>標(biāo)簽,在請(qǐng)求資源時(shí)都不存在跨域請(qǐng)求的限制。而jsonp則是基于<script>標(biāo)簽去請(qǐng)求資源,服務(wù)器則在返回?cái)?shù)據(jù)資源時(shí)將其包在一個(gè)本地可調(diào)用的全局函數(shù)里返回,然后本地則調(diào)用這個(gè)函數(shù)

原理詳解

首先因由于同源策略,我們無(wú)法通過(guò)ajax請(qǐng)求來(lái)獲取不符合條件的資源,但是假如我們用<script>標(biāo)簽請(qǐng)求的js文件中有這樣的語(yǔ)句:

test({"x": 1})

而我們前臺(tái)的請(qǐng)求如下:

<script src="http://xxx.xxx.com/xxx.js"></script>

那么獲取到請(qǐng)求以后就會(huì)執(zhí)行test這個(gè)函數(shù),并將一個(gè)對(duì)象作為參數(shù)傳入,此時(shí)如果我們的前臺(tái)本身有test這個(gè)函數(shù),那么就可以成功執(zhí)行這段代碼。換個(gè)說(shuō)法,如果現(xiàn)在我們通過(guò)訪問(wèn)一個(gè)后臺(tái)接口獲取到跟前面一樣的字符串(之前是通過(guò)訪問(wèn)一個(gè)遠(yuǎn)程js資源得到的),那么因?yàn)槎际怯?code><script>標(biāo)簽獲取的,獲取的結(jié)果也一樣,只是請(qǐng)求的url稍微有些改變,可以發(fā)現(xiàn)結(jié)果都是是一樣的:正常執(zhí)行test這個(gè)函數(shù),例如下面這段代碼:

<script src="http://xxx.xxx.com/api"></script>
<script>
function test(data) {
  console.log(data);
}
</script>

那么控制臺(tái)就會(huì)成功輸出data的內(nèi)容。這就是jsonp的原理,可以看出和ajax請(qǐng)求的本質(zhì)是完全不一樣的,ajax是基于XmlHttpRequest,而jsonp則是基于標(biāo)簽動(dòng)態(tài)請(qǐng)求,可以說(shuō)是一種偽請(qǐng)求

缺點(diǎn):由于是基于標(biāo)簽的src屬性請(qǐng)求的,只能使用get請(qǐng)求、并且安全性不好

基于CORS跨域資源共享配置

CORS是十分常用的一種跨域解決方案,其只需要在服務(wù)端配置對(duì)應(yīng)的響應(yīng)頭屬性即可,而無(wú)需前端做任何操作,最關(guān)鍵的就是在返回頭里配置Access-Control-Allow-Origin屬性,舉例(這里基于flask進(jìn)行示例):

from flask import Flask, make_response
import json

app = Flask(__name__)

@app.route('/test', methods=['GET'])
def user_info():
    response = make_response(json.dumps({'data': 'content'}))
    response.headers['Access-Control-Allow-Origin'] = '*'
    return response

if __name__ == '__main__':
    app.run(debug=True, port=5000)

還有一些其他訪問(wèn)控制的配置如下:

Access-Control-Allow-Methods: POST, GET, PUT, DELETE, HEAD, OPTIONS
# 允許的請(qǐng)求方式
#(注意只有POST, GET以及HEAD是默認(rèn)允許的請(qǐng)求,其他的請(qǐng)求都必須先發(fā)送一個(gè)OPTIONS的預(yù)請(qǐng)求
# 當(dāng)預(yù)請(qǐng)求得到認(rèn)可后才可以進(jìn)行請(qǐng)求,而只有在該配置當(dāng)中配置的方法才會(huì)得到認(rèn)可)
Access-Control-Allow-Headers: 'Content-Type, ...'
# 允許請(qǐng)求的頭部信息,沒(méi)有設(shè)置在里面的header都是不允許的
Access-Control-Max-Age: '1000'
# 設(shè)置允許跨域的時(shí)間,此時(shí)在有效期內(nèi)無(wú)需再發(fā)送預(yù)請(qǐng)求進(jìn)行驗(yàn)證,直接發(fā)請(qǐng)求就可以了,單位是s
Access-Control-Allow-Credentials: true
# 是否允許發(fā)送cookie

詳細(xì)參考:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials

缺點(diǎn):cors配置源地址只能配置*(多源)或者一個(gè)地址,并且如果配置的是*,那么基于安全性問(wèn)題,請(qǐng)求時(shí)將無(wú)法攜帶cookie

注:
如果不想配置成*,又希望多個(gè)地址可以,那么可以在程序當(dāng)中判斷訪問(wèn)的地址是否是允許的,如果是,則加入到cors配置當(dāng)中

基于Http Proxy

由于cors的弊端,又出了Http Proxy的方案,需要在webpack中配置,常在基于vue、react的前后端分離項(xiàng)目中使用,首先要安裝對(duì)應(yīng)模塊包:

npm install webpack webpack-dev-server

然后在webpack的配置文件webpack.config.js中配置:

devServer: {
  port: 8080,
  proxy: {
    '/': {
      target: 'http://127.0.0.1:6666',
      // 代理的地址
      // secure: false,
      // 如果是https接口,需要配置這個(gè)參數(shù)
      changeOrigin: true
      // 是否跨域
    }
  }
}
vue-cli中配置

如果是基于vue-cli的項(xiàng)目工程,那么這里介紹兩種方式實(shí)現(xiàn)proxy代理的配置:

  • 第一種:配置devServer,在build/webpack.base.conf.js中的module.exports進(jìn)行和上面相同的配置
  • 第二種:配置proxyTable,直接在config/index.js中的module.exports.dev配置如下:
module.exports = {
  dev: {
    ...
    proxyTable: {
        // 代理配置
        '/': {
          target: 'http://127.0.0.1:6666',
          changeOrigin: true,
          // pathRewrite: {
          //   '^/apis': '/api'  //重寫(xiě)的路徑  
          // }
        }
      },
  },
  ...
}

注:
配置proxy之后,實(shí)際上前端請(qǐng)求還是發(fā)給本身的node服務(wù)器(查看網(wǎng)絡(luò)請(qǐng)求就可以發(fā)現(xiàn)),例如前端是8080,配置了6666的代理,那么前端會(huì)先請(qǐng)求本身,即8080,然后8080再通過(guò)node去訪問(wèn)6666,由于服務(wù)器之間的請(qǐng)求不存在跨域問(wèn)題,所以6666返回?cái)?shù)據(jù)給node,node再返回給前端,由于同源,所以此時(shí)數(shù)據(jù)也就沒(méi)有跨域問(wèn)題了
注:
在vue-cli3.0+以后,則直接在項(xiàng)目根目錄下創(chuàng)建文件vue.config.js,并添加如下內(nèi)容即可:

module.exports = {
  devServer: {
    proxy: {
      "/": {
        target: "http://127.0.0.1:6666/",
        changeOrigin: true
      }
    }
  }
};
基于nginx配置CORS

在nginx中也可以配置cors,舉例:

server {
    listen 80;
    server_name  localhost;
    ...
    
    location / {
        root   html;
        index  index.html index.htm;
        proxy_pass http://xxx;
        add_header Access-Control-Allow-Origin *;  # 配置cors
        ...
    }
基于nginx配置反向代理

假如前臺(tái)服務(wù)端口為http://127.0.0.1:3000,后臺(tái)服務(wù)端接口為http://127.0.0.1:8000/api,那么因?yàn)椴煌矗厝淮嬖诳缬騿?wèn)題,此時(shí)可以在nginx中進(jìn)行配置如下:監(jiān)聽(tīng)3000端口,并配置/api的反向代理地址為:http://127.0.0.1:8000/api,配置文件示例如下:

server {
    listen 3000;
    server_name  localhost;
    ...
    
    location /api/{
        ...
        proxy_pass http://127.0.0.1:8000/api/;    # 配置反向代理
    }
}

再將前臺(tái)請(qǐng)求的接口改為:http://127.0.0.1:3000/api,即可解決跨域問(wèn)題。
(原理:經(jīng)過(guò)nginx的反向代理配置,現(xiàn)在訪問(wèn)http://127.0.0.1:3000/api就相當(dāng)于訪問(wèn)http://127.0.0.1:8000/api,而前臺(tái)請(qǐng)求的url因?yàn)楦某闪?code>http://127.0.0.1:3000/api,在瀏覽器看來(lái)前臺(tái)和請(qǐng)求接口同源,也就不存在跨域問(wèn)題了)

參考:https://blog.csdn.net/larger5/article/details/81286324

其他跨域解決方案

基于修改本地host文件(不推薦)

例如前臺(tái)地址:127.0.0.1:6666,而后臺(tái)接口地址:http://api.xxx.com,此時(shí)如果想要訪問(wèn)后臺(tái)接口,可以在本地host文件中加一行:

127.0.0.1:6666  http://api.xxx.com

但這種方式實(shí)際上只是在模仿同源請(qǐng)求,并沒(méi)有實(shí)質(zhì)地解決跨域問(wèn)題

基于Iframe的postMessage

postMessage方法允許頁(yè)面間基于Iframe進(jìn)行消息傳遞,例如A和B頁(yè)面進(jìn)行消息傳遞:

  • a.html
<html>
  <body>
    <iframe
      id="iframe"
      src="http://127.0.0.1:5500/b.html"
      style="display: none;"
    ></iframe>
  </body>
  <script>
    iframe.onload = function() {
      iframe.contentWindow.postMessage("來(lái)自A的信息...", "http://127.0.0.1:5500/b.html");
      // 發(fā)送消息給頁(yè)面B
    };
    // 監(jiān)聽(tīng)頁(yè)面B發(fā)來(lái)的消息
    window.onmessage = function(ev) {
      console.log("A收到B的信息:", ev.data);
    };
  </script>
</html>
  • b.html
<html>
  <body></body>
  <script>
    //   監(jiān)聽(tīng)頁(yè)面A發(fā)來(lái)的消息
    window.onmessage = function(ev) {
      console.log("B收到A的信息:", ev.data);
      ev.source.postMessage("回信...", "*");
    };
  </script>
</html>

參考:https://www.cnblogs.com/yyy6/p/9481671.html

基于H5的web socket

由于原生web socket不太好使用,這里使用socket.io(對(duì)web socket進(jìn)行了封裝的框架)進(jìn)行示例:

  • 服務(wù)端(基于node):
const server = require("http").createServer();
const io = require("socket.io")(server);
io.on("connection", client => {
  client.on("event", data => {
    console.log(data);
  });
  client.on("message", msg => {
    console.log(msg);
  });
  client.on("disconnect", () => {
    console.log("server has closed!");
  });
});
server.listen(3000);
  • 客戶(hù)端:
<html>
  <body></body>
  <script src="https://cdn.bootcss.com/socket.io/2.3.0/socket.io.js"></script>
  <script>
    let socket = io('http://127.0.0.1:3000/');

    socket.on('connect', () => {
      socket.on('message', msg => {
        console.log(msg);
      })

      socket.on('disconnect', () => {
        console.log('server has closed!');
      })
    })

    socket.send('test');

  </script>
</html>

web socket使用參考:https://zhuanlan.zhihu.com/p/74326818
使用WebSocket進(jìn)行跨域數(shù)據(jù)請(qǐng)求參考:https://blog.csdn.net/itkingone/article/details/83818278

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容