微服務(wù)Token鑒權(quán)設(shè)計的幾種方案

作者:JavaPub
編程學(xué)習(xí)一條龍:http://luxian.javapub.net.cn
原文:juejin.cn/post/7329352197837029385
Token透傳(不推薦)
剛開始接觸微服務(wù)時網(wǎng)上給的方案大都數(shù)是通過透傳Token做鑒權(quán),但我認(rèn)為這種方式不是很妥當(dāng)。接著往下看:

這種方式通過透傳Token使得各微服務(wù)都能獲取到當(dāng)前登錄人信息,在代碼編寫上確實可能會方便,但我認(rèn)為這不是一種很好的設(shè)計方式。
原因一:內(nèi)部API與外部API混合在一起不太好區(qū)分。
原因二:內(nèi)部調(diào)用的微服務(wù)API因該具備無狀態(tài)性質(zhì),這樣才能保證方法的原子性以提高代碼復(fù)用率。
換句話說:B服務(wù)提供API時不因該關(guān)心當(dāng)前是否為登錄狀態(tài),登錄狀態(tài)應(yīng)該由路由中的第一個服務(wù)校驗維護(hù),在調(diào)用后續(xù)服務(wù)時應(yīng)該顯示的傳入相關(guān)參數(shù)。比如以下場景:
場景一:用戶簽到添加積分
場景二:后臺管理員給用戶手動添加積分
場景三:分布式調(diào)度批量增加用戶積分
根據(jù)需求積分服務(wù)提供了一個給用戶添加積分的API,如果你的API是通過獲取的當(dāng)前登錄用戶ID增加的積分,那么面對場景二時你需要重新編寫一個給用戶添加積分的API,因為當(dāng)前登錄的是后臺管理員而不是用戶(代碼復(fù)用率較低)
不透傳數(shù)據(jù),顯示的提供入?yún)?/strong>

路由到達(dá)的第一個服務(wù)已經(jīng)對Token進(jìn)行了解析認(rèn)證并將userId顯示的傳遞給了后續(xù)服務(wù),后續(xù)服務(wù)不需要再對token進(jìn)行解析認(rèn)證。根據(jù)1.1的三個場景只需要提供一個入?yún)瑄serId的API,保證了函數(shù)的原子性提供代碼復(fù)用率。
注意: 提供的API不能暴露給外網(wǎng),我們需要在路徑上做區(qū)分,避免外網(wǎng)非法訪問內(nèi)部API。我們可以訂好內(nèi)部調(diào)用API路徑規(guī)則,如: /api/inside/\*\* 。在網(wǎng)關(guān)層拒絕內(nèi)部調(diào)用API請求的訪問。
統(tǒng)一授權(quán)
統(tǒng)一授權(quán)是指:將API鑒權(quán)集中在 應(yīng)用網(wǎng)關(guān) 上
Fegin內(nèi)部調(diào)用方式
Spring Cloud Gateway + Fegin內(nèi)部調(diào)用,集中在Gateway上做統(tǒng)一認(rèn)證鑒權(quán),鑒權(quán)后在請求頭中添加鑒權(quán)后的信息轉(zhuǎn)發(fā)給后續(xù)服務(wù),如:userId等。。。

缺點:A服務(wù)調(diào)用B服務(wù)時,B服務(wù)需要寫一個內(nèi)部調(diào)用的Controller接口A服務(wù)才能通過Fegin調(diào)用到B服務(wù),增加了代碼量(這里的設(shè)計方案是內(nèi)部調(diào)用與外部調(diào)用Controller是分開的)
Dubbo內(nèi)部調(diào)用方式
Spring Cloud Gateway + Dubbo內(nèi)部調(diào)用,集中在Gateway上做統(tǒng)一認(rèn)證鑒權(quán),鑒權(quán)后在請求頭中添加鑒權(quán)后的信息轉(zhuǎn)發(fā)給后續(xù)服務(wù),如:userId等。。。
優(yōu)點:與第一種相比不需要額外編寫一個Controller接口,只有本地service與遠(yuǎn)程DubboService的區(qū)別,代碼更簡潔。
缺點:項目技術(shù)棧略微增加了復(fù)雜度。

Spring Boot Web + Dubbo內(nèi)部調(diào)用方式
這里的設(shè)計方案直接去掉了Gateway,直接使用了一個Spring Boot Web項目來代替Gateway。但需要注意的是應(yīng)該將Web項目的容器換成Undertow,因為Tomcat是阻塞式的容器,不換也不是不行,但吞吐量可能會少一下,Undertow是非阻塞式的容器,可以與Gateway到達(dá)相同的效果。(非阻塞式:當(dāng)請求為線程進(jìn)入阻塞狀態(tài)時,當(dāng)前線程會被掛起,當(dāng)前的計算資源會去做別的事情,當(dāng)被掛起的線程收到響應(yīng)時才會被繼續(xù)執(zhí)行,壓榨CPU用更少的資源做更多的事情,但并不會提升性能)
因為去掉了Gateway我們需要將所有服務(wù)的Controller集成到Web應(yīng)用,然后在這個Web應(yīng)用上做統(tǒng)一認(rèn)證授權(quán)。如果將所有代碼寫到Web應(yīng)用中,這樣可能不合適,我們可以選擇每個服務(wù)創(chuàng)建一個Controller模塊,Web網(wǎng)關(guān)服務(wù)只有一個啟動類,通過依賴的方式集成所有服務(wù)的Controller。
優(yōu)點:簡化了項目結(jié)構(gòu),所有服務(wù)只有service代碼。性能壓測時不用考慮Gateway的線程池使用情況,業(yè)務(wù)服務(wù)只需要考慮Dubbo線程池的使用情況。
缺點:沒辦法通過配置中心動態(tài)調(diào)整路由。比如說增加了一個服務(wù)Gateway可以不重啟通過配置中心增加路由配置即可。

非統(tǒng)一授權(quán)
非統(tǒng)一授權(quán):不在應(yīng)用網(wǎng)關(guān)上集成鑒權(quán),網(wǎng)關(guān)只有單一的路由轉(zhuǎn)發(fā)業(yè)務(wù)。各位服務(wù)都有自己的鑒權(quán)方式,當(dāng)然也可以通過jar包的方式統(tǒng)一各服務(wù)的鑒權(quán)方式。
常規(guī)模式
通過編寫通用的鑒權(quán)模塊,各服務(wù)集成該模塊。該模塊具備以下功能:
- JWT Token解析
- 權(quán)限校驗攔截
- 緩存(本地緩存\Redis緩存)
這種模式更適合大型項目團(tuán)隊,可能各微服務(wù)都由一個項目組負(fù)責(zé)。各服務(wù)維護(hù)自己的權(quán)限規(guī)則(這里指的是權(quán)限規(guī)則數(shù)據(jù),規(guī)則是統(tǒng)一的)

該模式下由于應(yīng)用網(wǎng)關(guān)比較輕量級,不再涉及復(fù)雜的鑒權(quán)流程,使得項目部署可以更靈活,當(dāng)我們使用K8S部署項目時,我們可以將應(yīng)用網(wǎng)關(guān)替換成K8S中的Ingress網(wǎng)關(guān)。
我們先看常規(guī)模式部署在K8S中完整的鏈路:

當(dāng)用戶訪問時會先到達(dá)K8S Ingress網(wǎng)關(guān)通過應(yīng)用網(wǎng)關(guān)Service的負(fù)載均衡調(diào)用應(yīng)用網(wǎng)關(guān),應(yīng)用網(wǎng)關(guān)需要通過注冊中心獲取服務(wù)注冊列表,通過服務(wù)注冊列表負(fù)載均衡到后續(xù)服務(wù)。
與K8S集成
我們再來看看將應(yīng)用網(wǎng)關(guān)替換成K8S中的Ingress網(wǎng)關(guān)的完整鏈路:

這里不僅只是去掉了應(yīng)用網(wǎng)關(guān),同時我們通過K8S Service 負(fù)載均衡的能力去掉了注冊中心。減少了我們部署微服務(wù)時還要額外搭建一套注冊中心。同時減少了一層沒必要的轉(zhuǎn)發(fā)。至于K8S中的Service,大家可以理解成一個本地的host假域名,比如我們在K8S給商品創(chuàng)建一個Service,名稱為:goods-svc。那么我們可以通過goods-svc直連。如:
-
http://goods-svc:8080/api/goods/info/10001 -
dubbo://goods-svc:20880
方案沒有對錯,選擇適合自己的就是最好的。
