本文由圖雀社區成員 燦若星空 寫做而成,歡迎加入圖雀社區,一塊兒創做精彩的免費技術教程,予力編程行業發展。javascript
若是您以爲咱們寫得還不錯,記得 點贊 + 關注 + 評論 三連🥰🥰🥰,鼓勵咱們寫出更好的教程💪前端
隨着RESTful架構風格成爲主流,以及Vue.js、React.js和Angular.js這三大前端框架的日益強大,愈來愈多的開發者開始由傳統的MVC架構轉向基於先後端分離這一基礎架構來構建本身的系統,將前端頁面和後端服務分別部署在不一樣的域名之下。在此過程當中一個重要的問題就是跨域資源訪問的問題,一般因爲同域安全策略瀏覽器會攔截JavaScript腳本的跨域網絡請求,這也就形成了系統上線時前端沒法訪問後端資源這一問題。筆者將結合自身開發經驗,對這一問題產生的緣由以及相應的解決方案,給出詳細介紹。java
同源策略,它是由Netscape提出的一個著名的安全策略。如今全部支持JavaScript 的瀏覽器都會使用這個策略。所謂同源是指:協議、域名、端口相同。node
舉個例子:python
url | url | 是否同源 |
---|---|---|
test001.com/ | test001.com/ | 非同源 |
test001.com/ | test002.com/ | 非同源 |
test001.com:3000 | test001.com:4000 | 非同源 |
test001.com/userList | test001.com/orderList | 非同源 |
一個瀏覽器的兩個tab頁中分別打開來百度和谷歌的頁面,當瀏覽器的百度tab頁執行一個腳本的時候會檢查這個腳本是屬於哪一個頁面的,即檢查是否同源,只有和百度同源的腳本纔會被執行。若是非同源,那麼在請求數據時,瀏覽器會在控制檯中報一個異常,提示拒絕訪問。同源策略是瀏覽器的行爲,是爲了保護本地數據不被JavaScript代碼獲取回來的數據污染,所以攔截的是客戶端發出的請求回來的數據接收,即請求發送了,服務器響應了,可是沒法被瀏覽器接收。webpack
在前端開發階段,一些框架的腳手架工具會使用webpack-dev-serve來代理數據請求,其本質上是一個基於node.js的網頁服務器,因此感覺不到跨域資源訪問的限制。web
當網站上線後,網頁上不少資源都是要經過發送AJAX請求向服務器索要資源,可是在先後端分離的系統架構中,前端頁面和後端服務每每不會部署在同一域名之下。好比用戶經過瀏覽器訪問 www.test001.com 這一地址,來到了系統首頁,此時瀏覽器從網站服務器中只取回了基本的HTML頁面以及CSS樣式表文件和JavaScript腳本。系統首頁的其餘內容,好比輪播圖、文章列表等,須要利用JavaScript腳本程序,向地址爲 www.test002.com 的後端應用服務器發送請求來獲取信息。此時因爲瀏覽器的同源策略,該請求會被瀏覽器所攔截,這就形成了先後端數據不通這一結果。express
由於因爲瀏覽器的同源策略,JavaScript腳本程序只能向同一域名下的服務器發送網絡請求,那麼能夠經過網頁服務器轉發這一網絡請求到相應的後端服務器,獲取相關數據,而後網頁服務器再把這一數據返回給瀏覽器。這一過程稱之爲反向代理。npm
假設用戶經過地址www.test001.com訪問到了系統首頁,該系統首頁中所加載的JavaScript腳步程序本應該要發送AJAX請求到www.test002.com/api/article…這一地址,來獲取首頁文章列表信息。此時應該改爲向www.test001.com/api/article…這一與之同源的地址發送數據請求。該系統的網頁服務器會收到此請求,而後代替JavaScript腳本程序向www.test002.com/api/article…這一地址請求數據,獲取數據後將之返回給瀏覽器。此時JavaScript腳本程序就經過網頁服務器這一橋樑成功獲取到了後端應用服務器上的數據。編程
若服務器採用了寶塔面板這一管理軟件,能夠直接經過其提供的可視化界面進行反向代理的設置。對於一些新手而言,直接面對命令行進行各類操做,不夠直觀且難度較高,此時採用一些可視化的服務器管理軟件是一個不錯的選擇。
如果喜歡用vim 直接在命令行裏修改的同窗能夠參考這篇博客
這個解決方案是否是有些眼熟呢?
瀏覽器的同源策略對JavaScript腳本向不一樣域的服務器請求數據進行了限制,可是沒有對HTML中的<script>標籤進行限制,咱們能夠基於此規則,動態建立<script>標籤進行跨域資源訪問。<script>標籤中src這一屬性值設置爲:接口地址+處理數據的回調函數名稱。相關代碼示例以下:
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 設置接口地址+數據獲取成功後的回調函數(handleData)
script.src = 'http://www.test002.com/api/articleList&callback=handleData';
document.body.appendChild(script);
// 回調執行函數
function handleData(res) {
data = JSON.stringify(res)
console.log(data);
}
</script>
複製代碼
在這裏值得注意的是,由於請求數據的接口地址是寫在了<script>標籤中src這一屬性值裏面,那麼數據請求的方式就只能支持GET請求,其餘請求沒法實現。在基於Vue.js這種框架開發的項目中,由於其使用了虛擬化DOM這一律念,JSONP跨域的方式對其並非一個很好的選擇,對於原生JavaScript代碼,能夠採用此方式進行跨域。
跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個origin (domain)上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。
出於安全緣由,瀏覽器限制從腳本內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味着使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭! 因此要想實現跨域資源訪問,這也就要求後端服務程序,應該根據CORS策略來配置好相應的HTTP響應頭。
Access-Control-Allow-Origin: *
複製代碼
表示該資源能夠被任意外域訪問。
若是服務端僅容許來自 test001.com 的訪問,該首部字段的內容以下:
Access-Control-Allow-Origin: http://test001.com
複製代碼
在 Node.js 的輕量級 Web 框架 Express 中,咱們只須要安裝一個 cors 庫並添加此中間件便可配置好跨域問題:
npm install cors
複製代碼
而後在 Express 應用中使用這個中間件:
var express = require('express')
var cors = require('cors')
var app = express()
app.use(cors())
app.get('/products/:id', function (req, res, next) {
res.json({msg: 'This is CORS-enabled for all origins!'})
})
app.listen(80, function () {
console.log('CORS-enabled web server listening on port 80')
})
複製代碼
經過這樣的方式,就容許了全部的域名的請求方法。更多針對單個路由的跨域控制能夠參見 cors 文檔。
在以SpringBoot爲基礎框架的應用程序中能夠增長一個配置類進行CORS配置。具體代碼以下所示:
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Autowired
AdminInterceptor adminInterceptor;
//配置跨域相關
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins("*")
.allowedHeaders("*");
super.addCorsMappings(registry);
}
}
複製代碼
上述代碼是較爲粗獷的解決方案,即容許了全部域名的全部請求方法。在實際開發過程當中應對於所收到請求的請求路徑、請求方法、源、請求頭加以限制,以確保服務的安全。繼續以上述例子說明,安全的配置應該以下:
//配置跨域相關
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedMethods("GET")
.allowedOrigins("www.test001.com")
.allowedHeaders("*");
super.addCorsMappings(registry);
}
複製代碼
在這種配置中只有來自域名www.test001.com才能夠訪問服務器數據,並且只接受GET方式的數據請求,對於訪問路徑也作了限制,只有/api開頭的路徑才能訪問的到。這樣就進一步保證了後端應用程序的安全性。
在以Flask這一輕量級web服務框架爲基礎所開發的應用服務中,首先要安裝flask跨域資源共享庫,可以使用命令pip install flask_cors。接下來可按照以下代碼進行CORS配置。
from flask_cors import CORS
app = Flask(__name__)
CORS(app, supports_credentials=True)
複製代碼
跨域問題在目先後端分離的架構中廣泛存在,本文所介紹的這幾種方案雖然都可以解決跨域問題,但其實各有優劣。好比Jsonp方式實現起來較爲簡單,但只支持GET請求方式,在原生JavaScript腳本中使用方便,可是當利用瞭如Vue.js這種MVVM框架時就有些難以施展了。反向代理的方式無需改動後端代碼,可是對於整個系統而言可移植性較差,CORS方式須要後端來積極配合前端實現跨域。總之,沒有技術銀彈,咱們要在實際情形中比較分析,選擇最合適的方案。
若是您以爲咱們寫得還不錯,記得 點贊 + 關注 + 評論 三連🥰🥰🥰,鼓勵咱們寫出更好的教程💪
想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。