本博客配套的源碼在這里
一、概述
1.問(wèn)題場(chǎng)景
最近我在做一個(gè)系統(tǒng)的全棧開發(fā),遇到了這樣一個(gè)問(wèn)題。
首先,我的前端是一個(gè)來(lái)自百度的開源框架——Amis,它封裝自React.js,基于JSON配置。我下載了Amis提供的SDK文件夾,并進(jìn)行了代碼開發(fā)。但是我在部署整個(gè)系統(tǒng)的時(shí)候遇到了跨域問(wèn)題。原因是,我的前端不是以服務(wù)的形式運(yùn)行的,它是一組在瀏覽器中打開的HTML頁(yè)面。
如果我在瀏覽器中打開一個(gè)HTML頁(yè)面,當(dāng)前采用的協(xié)議通常是HTTP或HTTPS,域名通常是"localhost"或者是HTML文件所在的服務(wù)器的域名,端口通常是80(HTTP)或443(HTTPS)。由于我是通過(guò)文件路徑直接打開HTML文件,那么協(xié)議、域名和端口都是本地文件系統(tǒng)的相關(guān)信息。
而我需要在前端中調(diào)用后端的接口,盡管后端IP與前端一致,但PORT和前端不同,因此在瀏覽器中訪問(wèn)系統(tǒng)時(shí),觸發(fā)了瀏覽器的同源策略,導(dǎo)致我的前端無(wú)法訪問(wèn)后端接口。
2.瀏覽器的同源策略
根據(jù)同源策略,瀏覽器會(huì)阻止頁(yè)面中的JavaScript代碼向不同域名、協(xié)議或端口的資源發(fā)出跨域請(qǐng)求。這意味著如果我的HTML頁(yè)面和后端服務(wù)的域名、協(xié)議或端口不一致,瀏覽器會(huì)阻止這種跨域請(qǐng)求。
3.解決思路
我上網(wǎng)查閱了資料,發(fā)現(xiàn),在遵守同源策略的前提下,可以采取以下方法來(lái)實(shí)現(xiàn)前端頁(yè)面對(duì)后端服務(wù)的訪問(wèn):
- 使用CORS(跨域資源共享):在后端服務(wù)中配置允許特定域名的跨域請(qǐng)求,通過(guò)設(shè)置響應(yīng)頭來(lái)允許跨域訪問(wèn)。這樣可以讓前端頁(yè)面在瀏覽器中向后端服務(wù)發(fā)出跨域請(qǐng)求。
- JSONP(JSON with Padding):在一些舊版瀏覽器中,可以通過(guò)JSONP來(lái)進(jìn)行跨域請(qǐng)求。不過(guò)需要注意JSONP存在一些安全性方面的問(wèn)題,需要謹(jǐn)慎使用。
- 代理服務(wù)器:在開發(fā)環(huán)境中,可以設(shè)置代理服務(wù)器來(lái)轉(zhuǎn)發(fā)前端請(qǐng)求到后端服務(wù),使得前端頁(yè)面和后端服務(wù)在同一個(gè)域名下,從而避免跨域問(wèn)題。
下面我將對(duì)這三種方法進(jìn)行實(shí)踐。
二、一點(diǎn)準(zhǔn)備工作
1.創(chuàng)建前端工程1
前端工程1我寫了一個(gè)HTML頁(yè)面,放在HTML目錄下。在點(diǎn)擊按鈕后,顯示彈窗,如果后端順利返回,則將返回的文本顯示到彈窗上;如果發(fā)生異常,在彈窗上顯示異常信息。
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>調(diào)用后端服務(wù)</title>
</head>
<body>
<h1>調(diào)用后端服務(wù)示例</h1>
<button id="getDataBtn">獲取數(shù)據(jù)</button>
<script>
document.getElementById('getDataBtn').addEventListener('click', function() {
fetch('http://127.0.0.1:2020/api/hello')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
})
.then(data => {
alert(data);
})
.catch(error => {
alert('發(fā)生錯(cuò)誤: ' + error.message);
});
});
</script>
</body>
</html>
2.創(chuàng)建后端工程
我創(chuàng)建了一個(gè)Flask項(xiàng)目,因?yàn)镕lask足夠的簡(jiǎn)單、快捷,但是你也可以使用任何你熟悉的語(yǔ)言和框架。
使用之前需要本地有python環(huán)境,并執(zhí)行pip install flask
來(lái)安裝依賴,并在我提供的源碼的FLASK目錄下執(zhí)行python app.py
來(lái)運(yùn)行項(xiàng)目。
app.py
from flask import Flask
app = Flask(__name__)
@app.route('/api/hello', methods=['GET'])
def get_data():
return jsonify(data='hello,flask!')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=2020,debug=True)
3.創(chuàng)建前端工程2
我想找到不同形式的前端對(duì)應(yīng)的跨域問(wèn)題的解決方案,因此我創(chuàng)建了兩種前端,除了上面的HTML頁(yè)面的形式,還包括了服務(wù)的形式。
我創(chuàng)建了一個(gè)vue.js腳手架項(xiàng)目,并在里面寫了和前端工程1類似的代碼。關(guān)于怎么快速上手vue,可以看我的另一篇博客:這里
在VUE/vue-app目錄下執(zhí)行npm run serve
運(yùn)行項(xiàng)目。
App.vue
<template>
<div>
<button @click="getData">獲取數(shù)據(jù)</button>
<div v-if="showModal" class="modal">
<div class="modal-content">
<span v-if="responseData">{{ responseData }}</span>
<span v-if="error">{{ error }}</span>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
showModal: false,
responseData: null,
error: null
};
},
methods: {
getData() {
fetch('http://127.0.0.1:2020/api/hello')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
})
.then(data => {
this.responseData = data;
this.showModal = true;
})
.catch(error => {
this.error = '發(fā)生錯(cuò)誤: ' + error.message;
this.showModal = true;
});
}
}
};
</script>
<style>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
}
</style>
4.跨域問(wèn)題
- 前端工程1——HTML:
在瀏覽器中打開home.html頁(yè)面,點(diǎn)擊按鈕后,無(wú)法成功獲取后端數(shù)據(jù),觸發(fā)了瀏覽器的同源策略。
- 前端工程2——VUE:
下面就來(lái)著手解決這個(gè)問(wèn)題。
三、方法1:使用CORS
在后端服務(wù)中配置允許特定域名的跨域請(qǐng)求,通過(guò)設(shè)置響應(yīng)頭來(lái)允許跨域訪問(wèn)。這樣可以讓前端頁(yè)面在瀏覽器中向后端服務(wù)發(fā)出跨域請(qǐng)求。
在 Flask 項(xiàng)目中應(yīng)用
CORS(app)
的底層原理涉及到在 HTTP響應(yīng)中添加特定的頭部信息,以允許跨域請(qǐng)求訪問(wèn)資源。Flask-CORS 擴(kuò)展簡(jiǎn)化了這個(gè)過(guò)程,它通過(guò)在響應(yīng)中添加適當(dāng)?shù)?CORS頭部信息來(lái)實(shí)現(xiàn)跨域資源共享。 具體來(lái)說(shuō),當(dāng)你在 Flask 項(xiàng)目中調(diào)用CORS(app)
時(shí),F(xiàn)lask-CORS會(huì)自動(dòng)為你的應(yīng)用程序添加 CORS 頭部信息,包括Access-Control-Allow-Origin
、Access-Control-Allow-Methods
、Access-Control-Allow-Headers
等。這些頭部信息告訴瀏覽器哪些跨域請(qǐng)求是被允許的,從而解決了跨域請(qǐng)求被瀏覽器阻止的問(wèn)題。
Flask-CORS 簡(jiǎn)化了這個(gè)過(guò)程。
這種方法只能對(duì)前端工程是服務(wù)(前端工程2)
的情況生效,原因就是后端需要配置前端的ip、端口等信息,而前端工程1是以文件的形式
打開前端頁(yè)面并訪問(wèn)后端的。
當(dāng)以文件路徑的形式在瀏覽器中打開HTML頁(yè)面文件時(shí),頁(yè)面中調(diào)用后端的API時(shí),無(wú)法直接獲取調(diào)用者的IP和端口。這是因?yàn)橐晕募窂叫问酱蜷_HTML頁(yè)面時(shí),頁(yè)面的請(qǐng)求是直接從文件系統(tǒng)發(fā)出的,而不是通過(guò)網(wǎng)絡(luò)協(xié)議進(jìn)行通信,因此無(wú)法獲取調(diào)用者的IP和端口信息。
如果需要獲取調(diào)用者的IP和端口信息,需要將HTML頁(yè)面部署到一個(gè)服務(wù)器上,然后通過(guò)服務(wù)器地址訪問(wèn)頁(yè)面,這樣頁(yè)面中的請(qǐng)求就會(huì)通過(guò)網(wǎng)絡(luò)協(xié)議進(jìn)行通信,從而可以獲取調(diào)用者的IP和端口信息。
- 后端配置CORS:
from flask import Flask,request
from flask_cors import CORS
app = Flask(__name__)
# 配置前端vue的ip、端口
CORS(app, resources={r"/api/hello": {"origins": "http://127.0.0.1:8080"}})
@app.route('/api/hello', methods=['GET'])
def get_data():
return jsonify(data='hello,flask!')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=2020,debug=True)
- 在瀏覽器中訪問(wèn)vue頁(yè)面:
成功訪問(wèn)到了后端!
四、方法2:JSONP
JSONP(JSON with Padding)是一種利用<script>標(biāo)簽的跨域技術(shù),它允許在不受同源策略限制的情況下從其他域中獲取數(shù)據(jù)。
底層原理是利用
<script>
標(biāo)簽的跨域特性來(lái)實(shí)現(xiàn)跨域請(qǐng)求。JSONP是一種在客戶端與服務(wù)器之間進(jìn)行跨域數(shù)據(jù)傳輸?shù)募夹g(shù),它允許從其他域中獲取數(shù)據(jù),繞過(guò)了瀏覽器的同源策略限制。 JSONP的基本原理是通過(guò)在頁(yè)面上動(dòng)態(tài)創(chuàng)建一個(gè)<script>
標(biāo)簽,該標(biāo)簽的 src 屬性指向包含 JSON 數(shù)據(jù)的 URL 地址。這個(gè) URL地址會(huì)將 JSON 數(shù)據(jù)包裹在一個(gè)函數(shù)調(diào)用中,這個(gè)函數(shù)是在客戶端預(yù)先定義好的。服務(wù)器返回的數(shù)據(jù)會(huì)被當(dāng)做 JavaScript代碼執(zhí)行,從而觸發(fā)客戶端預(yù)先定義的函數(shù),實(shí)現(xiàn)對(duì)數(shù)據(jù)的處理和展示。
需要注意的是,JSONP存在一些安全性方面的問(wèn)題,主要是潛在的跨站腳本攻擊(XSS)風(fēng)險(xiǎn)。因?yàn)镴SONP是通過(guò)動(dòng)態(tài)創(chuàng)建<script>
標(biāo)簽來(lái)獲取數(shù)據(jù)的,所以如果被惡意注入了惡意代碼,就有可能導(dǎo)致安全問(wèn)題。因此,在使用JSONP時(shí)需要謹(jǐn)慎處理返回的數(shù)據(jù),確保數(shù)據(jù)的安全性。
- 修改前端工程1的代碼:
home.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>調(diào)用后端服務(wù)</title>
</head>
<body>
<h1>使用JSONP</h1>
<div id="result"></div>
<button id="getDataBtn">獲取數(shù)據(jù)</button>
<script>
document.getElementById('getDataBtn').addEventListener('click', function() {
var script = document.createElement('script');
script.src = 'http://127.0.0.1:2020/api/hello?callback=handleData';
document.body.appendChild(script);
});
function handleData(data) {
document.getElementById('result').innerText = data.result;
}
</script>
</body>
</html>
失??!觸發(fā)了瀏覽器的CORB策略!
這個(gè)錯(cuò)誤表明瀏覽器使用了CORB(Cross-Origin Read Blocking)機(jī)制來(lái)阻止跨域讀取。CORB是一種安全機(jī)制,用于防止惡意網(wǎng)站從跨域響應(yīng)中讀取敏感數(shù)據(jù)。瀏覽器對(duì)JSONP請(qǐng)求進(jìn)行了CORB阻止。JSONP本身存在安全風(fēng)險(xiǎn),因?yàn)樗峭ㄟ^(guò)動(dòng)態(tài)創(chuàng)建<script>標(biāo)簽來(lái)獲取數(shù)據(jù)的,這可能導(dǎo)致惡意網(wǎng)站注入惡意代碼。因此,瀏覽器會(huì)對(duì)JSONP請(qǐng)求進(jìn)行CORB阻止。
五、方法3:Nginx
在開發(fā)環(huán)境中,可以設(shè)置代理服務(wù)器來(lái)轉(zhuǎn)發(fā)前端請(qǐng)求到后端服務(wù),使得前端頁(yè)面和后端服務(wù)在同一個(gè)域名下,從而避免跨域問(wèn)題。我以Nginx為例:
Nginx 解決跨域問(wèn)題的底層原理主要是通過(guò)設(shè)置 HTTP 響應(yīng)頭來(lái)實(shí)現(xiàn)跨域資源共享(CORS)。當(dāng)瀏覽器發(fā)起跨域請(qǐng)求時(shí),會(huì)先發(fā)送一個(gè)OPTIONS 預(yù)檢請(qǐng)求,以確定是否允許跨域訪問(wèn)。Nginx 可以通過(guò)設(shè)置響應(yīng)頭來(lái)響應(yīng)這個(gè)預(yù)檢請(qǐng)求,從而允許跨域訪問(wèn)。
具體來(lái)說(shuō),Nginx 可以通過(guò)設(shè)置 add_header 指令來(lái)添加Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers
等 CORS 相關(guān)的響應(yīng)頭,允許特定域名的跨域請(qǐng)求訪問(wèn)資源。這樣一來(lái),瀏覽器就能夠允許跨域請(qǐng)求的發(fā)送和接收,從而解決了跨域問(wèn)題。
關(guān)于Nginx詳細(xì)的學(xué)習(xí)和使用可以參考我的這篇博客:Nginx
1.安裝和啟動(dòng)(windows)
-
下載: 下載 Nginx 的 Windows 版本安裝文件(https://nginx.org/en/download.html)。
-
解壓: 下載完成后,解壓縮安裝文件到你選擇的目錄。
-
啟動(dòng): 在解壓文件夾中雙擊nginx.exe就能啟動(dòng)nginx;或者在當(dāng)前目錄下打開終端執(zhí)行
nginx
命令。如果一切順利,在瀏覽器中輸入http://localhost
將訪問(wèn) Nginx 的歡迎頁(yè)面。 -
配置文件:
nginx.conf
文件位于安裝目錄下的conf
文件夾中,可用文本編輯器打開并進(jìn)行修改。
2.使用Nginx配置轉(zhuǎn)發(fā)規(guī)則
這一章我以前端工程1為例,前端工程2是vue服務(wù),執(zhí)行npm run build
對(duì)項(xiàng)目打包成靜態(tài)文件,都存放在了dist目錄中,后續(xù)nginx配置流程和前端工程1是一樣的。
nginx.conf:
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8000;
server_name 192.168.2.107;
# 配置后端工程:
# 訪問(wèn)http://192.168.2.107:8000/flask/
# 時(shí)相當(dāng)于訪問(wèn)http://192.168.2.107:2020/
location /flask/ {
proxy_pass http://192.168.2.107:2020/;
}
# 配置前端工程1:這個(gè)要放在最下面
# 訪問(wèn)http://192.168.2.107:8000/home.html
# 時(shí)相當(dāng)于在瀏覽器中訪問(wèn)HTML文件夾
location / {
root "D:\\0 project\\cors\\HTML";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
在nginx安裝目錄下執(zhí)行nginx -s reload
刷新配置。
3.修改后端工程的ip
將原來(lái)的127.0.0.1改成服務(wù)器局域網(wǎng)ip(僅對(duì)局域網(wǎng)可見),如果有公網(wǎng)ip是最好的,但是我電腦沒(méi)配置這個(gè)。
if __name__ == '__main__':
app.run(host='192.168.2.107', port=2020,debug=True)
4.前端代碼修改
home.html文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-806939.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nginx示例</title>
</head>
<body>
<h1>Nginx示例</h1>
<button id="getDataBtn">獲取數(shù)據(jù)</button>
<script>
document.getElementById('getDataBtn').addEventListener('click', function() {
fetch('http://192.168.2.107:8000/flask/api/hello')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.text();
})
.then(data => {
alert(data);
})
.catch(error => {
alert('發(fā)生錯(cuò)誤: ' + error.message);
});
});
</script>
</body>
</html>
- 最后,看看效果:
跨域問(wèn)題通過(guò)Nginx解決了!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-806939.html
到了這里,關(guān)于解決系統(tǒng)開發(fā)中的跨域問(wèn)題:CORS、JSONP、Nginx的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!