前面我們了解了Gateway組件的過(guò)濾器挚币,這一節(jié)我們就探討一下Gateway在分布式環(huán)境中的一個(gè)具體用例-用戶(hù)鑒權(quán)月帝。
傳統(tǒng)單應(yīng)用的用戶(hù)鑒權(quán)
從我們開(kāi)始學(xué)JavaEE的時(shí)候始赎,就被洗腦式灌輸了一種權(quán)限驗(yàn)證的標(biāo)準(zhǔn)做法矮台,那就是將用戶(hù)的登錄狀態(tài)保存到HttpSession中,比如在登錄成功后保存一對(duì)key-value值到session欲间,key是userId而value是用戶(hù)后臺(tái)的真實(shí)ID荤傲。接著創(chuàng)建一個(gè)ServletFilter過(guò)濾器耻涛,用來(lái)攔截需要登錄才能訪(fǎng)問(wèn)的資源呐伞,假如這個(gè)請(qǐng)求對(duì)應(yīng)的服務(wù)端session里找不到userId這個(gè)key,那么就代表用戶(hù)尚未登錄慎式,這時(shí)候可以直接拒絕服務(wù)然后重定向到用戶(hù)登錄頁(yè)面伶氢。
大家應(yīng)該都對(duì)session機(jī)制比較熟悉,它和cookie是相互依賴(lài)的瘪吏,cookie是存放在用戶(hù)瀏覽器中的信息癣防,而session則是存放在服務(wù)器端的。當(dāng)瀏覽器發(fā)起服務(wù)請(qǐng)求的時(shí)候就會(huì)帶上cookie掌眠,服務(wù)器端接到Request后根據(jù)cookie中的jsessionid拿到對(duì)應(yīng)的session蕾盯。
由于我們只啟動(dòng)一臺(tái)服務(wù)器,所以在登錄后保存的session始終都在這臺(tái)服務(wù)器中蓝丙,可以很方便的獲取到session中的所有信息级遭。用這野路子,我們一路搞定了各種課程作業(yè)和畢業(yè)設(shè)計(jì)渺尘。結(jié)果一到工作崗位發(fā)現(xiàn)行不通了挫鸽,因?yàn)樗袘?yīng)用都是集群部署,在一臺(tái)機(jī)器保存了的session無(wú)法同步到其他機(jī)器上鸥跟。那我們有什么成熟的解決方案嗎丢郊?
分布式環(huán)境下的解決方案
同步session
Session復(fù)制是最容易先想到的解決方案,我們可以把一臺(tái)機(jī)器中的session復(fù)制到集群中的其他機(jī)器医咨。比如Tomcat中也有內(nèi)置的session同步方案枫匾,但是這并不是一個(gè)很優(yōu)雅的解決方案,它會(huì)帶來(lái)以下兩個(gè)問(wèn)題:
? Timing問(wèn)題 同步需要花費(fèi)一定的時(shí)間拟淮,我們無(wú)法保證session同步的及時(shí)性干茉,也就是說(shuō),當(dāng)用戶(hù)發(fā)起的兩個(gè)請(qǐng)求分別落在不同機(jī)器上的時(shí)候惩歉,前一個(gè)請(qǐng)求寫(xiě)入session的信息可能還沒(méi)同步到所有機(jī)器等脂,后一個(gè)請(qǐng)求就已經(jīng)開(kāi)始執(zhí)行業(yè)務(wù)邏輯了俏蛮,這不免引起臟讀幻讀。
? 數(shù)據(jù)冗余 所有服務(wù)器都需要保存一份session全集上遥,這就產(chǎn)生了大量的冗余數(shù)據(jù)
反向代理:綁定IP或一致性Hash
這個(gè)方案可以放在Nignx網(wǎng)關(guān)層做的搏屑,我們可以指定某些IP段的請(qǐng)求落在某個(gè)指定機(jī)器上,這樣一來(lái)session始終只存在一臺(tái)機(jī)器上粉楚。不過(guò)相比前一種session復(fù)制的方法來(lái)說(shuō)辣恋,綁定IP的方式有更明顯的缺陷:
? 負(fù)載均衡 在綁定IP的情況下無(wú)法在網(wǎng)關(guān)層應(yīng)用負(fù)載均衡策略,而且某個(gè)服務(wù)器出現(xiàn)故障的話(huà)會(huì)對(duì)指定IP段的來(lái)訪(fǎng)用戶(hù)產(chǎn)生較大影響模软。對(duì)網(wǎng)關(guān)層來(lái)說(shuō)該方案的路由規(guī)則配置也極其麻煩伟骨。
? IP變更 很多網(wǎng)絡(luò)運(yùn)營(yíng)商會(huì)時(shí)不時(shí)切換用戶(hù)IP,這就會(huì)導(dǎo)致更換IP后的請(qǐng)求被路由到不同的服務(wù)節(jié)點(diǎn)處理燃异,這樣一來(lái)就讀不到前面設(shè)置的session信息了
為了解決第二個(gè)問(wèn)題携狭,可以通過(guò)一致性Hash的路由方案來(lái)做路由,比如根據(jù)用戶(hù)ID做Hash回俐,不同的Hash值落在不同的機(jī)器上逛腿,保證足夠均勻的分配,這樣也就避免了IP切換的問(wèn)題仅颇,但依然無(wú)法解決第一點(diǎn)里提到的負(fù)載均衡問(wèn)題单默。
Redis解決方案
這個(gè)方案解決了前面提到的大部分問(wèn)題,session不再保存在服務(wù)器上忘瓦,取而代之的是保存在redis中搁廓,所有的服務(wù)器都向redis寫(xiě)入/讀取緩存信息。
在Tomcat層面耕皮,我們可以直接引入tomcat-redis-session-manager組件境蜕,將容器層面的session組件替換為基于redis的組件,但是這種方案和容器綁定的比較緊密明场。另一個(gè)更優(yōu)雅的方案是借助spring-session管理redis中的session汽摹,盡管這個(gè)方案脫離了具體容器,但依然是基于Session的用戶(hù)鑒權(quán)方案苦锨,這類(lèi)Session方案已經(jīng)在微服務(wù)應(yīng)用中被淘汰了逼泣。
分布式Session的替代方案
To think out of box guys~讓我們把session拋到腦后,看看現(xiàn)在流行的兩種認(rèn)證方式:
OAuth 2.0
大家一定用過(guò)現(xiàn)在比較流行的第三方登錄舟舒,比如我們通過(guò)微信掃碼登錄就可以登錄某個(gè)應(yīng)用的在線(xiàn)系統(tǒng)拉庶,但是這個(gè)應(yīng)用并不知道我的微信用戶(hù)名和密碼。這便是我們要介紹的第一個(gè)鑒權(quán)方案-OAuth 2.0秃励。
OAuth 2.0是一個(gè)開(kāi)放授權(quán)標(biāo)準(zhǔn)協(xié)議氏仗,它允許用戶(hù)讓第三方應(yīng)用訪(fǎng)問(wèn)該用戶(hù)在某服務(wù)的特定私有資源,但是不提供賬號(hào)密碼信息給第三方應(yīng)用。在上面的例子中皆尔,微信就相當(dāng)于一個(gè)第三方應(yīng)用呐舔,我們通過(guò)OAuth 2.0
拿微信登錄第三方應(yīng)用的例子來(lái)說(shuō):
? Auth Grant 在這一步Client發(fā)起Authorization Request到微信系統(tǒng)(比如通過(guò)微信內(nèi)掃碼授權(quán)),當(dāng)身份驗(yàn)證成功后獲取Auth Grant
? Get Token 客戶(hù)端拿著從微信獲取到的Auth Grant慷蠕,發(fā)給第三方引用的鑒權(quán)服務(wù)珊拼,換取一個(gè)Token,這個(gè)Token就是訪(fǎng)問(wèn)第三方應(yīng)用資源所需要的令牌
? 訪(fǎng)問(wèn)資源 最后一步流炕,客戶(hù)端在請(qǐng)求資源的時(shí)候帶上Token令牌澎现,服務(wù)端驗(yàn)證令牌真實(shí)有效后即返回指定資源
我們可以借助Spring Cloud中內(nèi)置的
spring-cloud-starter-oauth2
組件搭建OAuth 2.0的鑒權(quán)服務(wù),OAuth 2.0的協(xié)議還涉及到很多復(fù)雜的規(guī)范每辟,比如角色剑辫、客戶(hù)端類(lèi)型、授權(quán)模式等渠欺。這一小節(jié)我們暫且不深入探討OAuth 2.0的實(shí)現(xiàn)方式妹蔽,先來(lái)看另外一個(gè)更輕量級(jí)的授權(quán)方案:JWT鑒權(quán)。
JWT鑒權(quán)
JWT也是一種基于Token的鑒權(quán)機(jī)制挠将,它的基本思想就是通過(guò)用戶(hù)名+密碼換取一個(gè)Access Token
鑒權(quán)流程
相比OAuth 2.0來(lái)說(shuō)讹开,它的鑒權(quán)過(guò)程更加簡(jiǎn)單,其基本流程是這樣的:
- 用戶(hù)名+密碼訪(fǎng)問(wèn)鑒權(quán)服務(wù)驗(yàn)證通過(guò):服務(wù)器返回一個(gè)Access Token給客戶(hù)端捐名,并將token保存在服務(wù)端某個(gè)地方用于后面的訪(fǎng)問(wèn)控制(可以保存在數(shù)據(jù)庫(kù)或者Redis中)
a. 驗(yàn)證失敗:不生成Token - 客戶(hù)端使用令牌訪(fǎng)問(wèn)資源闹击,服務(wù)器驗(yàn)證令牌有效性令牌錯(cuò)誤或過(guò)期:攔截請(qǐng)求镶蹋,讓客戶(hù)端重新申請(qǐng)令牌
a. 令牌正確:允許放行
Access Token中的內(nèi)容
JWT的Access Token由三個(gè)部分構(gòu)成,分別是Header赏半、Payload和Signature贺归,我們分別看下這三個(gè)部分都包含了哪些信息:
? Header 頭部聲明了Token的類(lèi)型(JWT類(lèi)型)和采用的加密算法(HS256)
{'typ': 'JWT',
'alg': 'HS256'}
? Payload 這一段包含的信息相當(dāng)豐富,你可以定義Token簽發(fā)者断箫、簽發(fā)和過(guò)期時(shí)間拂酣、生效時(shí)間等一系列屬性,還可以添加自定義屬性仲义。服務(wù)端收到Token的時(shí)候也同樣可以對(duì)Payload中包含的信息做驗(yàn)證婶熬,比如說(shuō)某個(gè)Token的簽發(fā)者是“Feign-API”,假如某個(gè)接口只能允許“Gateway-API”簽發(fā)的Token埃撵,那么在做鑒權(quán)服務(wù)時(shí)就可以加入Issuer的判斷邏輯赵颅。
? Signature 它會(huì)使用Header和Payload以及一個(gè)密鑰用來(lái)生成簽證信息,這一步會(huì)使用Header里我們指定的加密算法進(jìn)行加密
目前實(shí)現(xiàn)JWT的開(kāi)源組件非常多暂刘,如果決定使用這個(gè)方案饺谬,只要添加任意一個(gè)開(kāi)源JWT實(shí)現(xiàn)的依賴(lài)項(xiàng)到項(xiàng)目中的pom文件,然后在加解密時(shí)調(diào)用該組件來(lái)完成谣拣。