侯海平
安徽財(cái)貿(mào)職業(yè)學(xué)院 信息工程學(xué)院,安徽 合肥 230061
線上業(yè)務(wù)的處理能力越來越受到企業(yè)關(guān)注,大量線下業(yè)務(wù)都逐步遷移到線上進(jìn)行處理,微服務(wù)架構(gòu)也成為在線流量大、并發(fā)性高業(yè)務(wù)場(chǎng)景中的必選架構(gòu),大量業(yè)務(wù)系統(tǒng)由單體架構(gòu)向微服務(wù)架構(gòu)遷移[1]。無論是單體架構(gòu)系統(tǒng)還是微服務(wù)架構(gòu)系統(tǒng)都需要對(duì)業(yè)務(wù)資源實(shí)施授權(quán)訪問機(jī)制,防止不具備權(quán)限的用戶賬號(hào)訪問不匹配的內(nèi)容資源。微服務(wù)架構(gòu)下系統(tǒng)資源分散、系統(tǒng)開發(fā)技術(shù)多樣化、系統(tǒng)模塊相對(duì)獨(dú)立給授權(quán)認(rèn)證帶來困難。
目前應(yīng)用領(lǐng)域還存在大量的單體應(yīng)用,例如一些傳統(tǒng)的業(yè)務(wù)系統(tǒng)如OA、財(cái)務(wù)系統(tǒng)、圖書借閱系統(tǒng)等,它們?cè)谟脩魯?shù)量相對(duì)有限和穩(wěn)定的情況下依然能夠滿足業(yè)務(wù)運(yùn)營(yíng)的日常需求,因此依然有存在和使用的價(jià)值。所謂單體架構(gòu)是指所有的業(yè)務(wù)系統(tǒng)功能都集成在一個(gè)開發(fā)項(xiàng)目中,這些業(yè)務(wù)代碼最終將被打包到一個(gè)程序中,該程序運(yùn)行在操作系統(tǒng)中以一個(gè)進(jìn)程為載體運(yùn)行。例如Java 項(xiàng)目會(huì)使用Maven包管理工具將項(xiàng)目打包成一個(gè)war 包或一個(gè)jar包,然后以一個(gè)進(jìn)程的形式運(yùn)行在Tomcat 服務(wù)器程序中或直接運(yùn)行在Java 虛擬機(jī)中。如果是.net(微軟平臺(tái)下)項(xiàng)目則會(huì)以一個(gè)dll 文件作為項(xiàng)目入口,運(yùn)行在IIS 服務(wù)器程序中。通常這些單體架構(gòu)只會(huì)映射到一個(gè)IP 和端口上,對(duì)外界提供服務(wù)。
在單體架構(gòu)中,驗(yàn)證用戶賬號(hào)、密碼的組件與鑒別用戶身份的組件,以及與提供受限資源的組件都是在同一個(gè)Web 程序中,因此只需考慮同一用戶同一session 會(huì)話周期即可。一次鑒權(quán)過程主要包括2 個(gè)主要活動(dòng):第一,用戶提交賬號(hào)和密碼登錄,服務(wù)驗(yàn)證賬號(hào)和密碼;第二,用戶成功登錄后,訪問受限資源,服務(wù)器鑒權(quán)后提供受限資源給用戶。
認(rèn)證活動(dòng)的步驟:
步驟1:用戶在瀏覽器上輸入賬號(hào)和密碼提交至服務(wù)器進(jìn)行驗(yàn)證;
步驟2:服務(wù)器收到用戶請(qǐng)求的賬號(hào)和密碼,對(duì)其進(jìn)行驗(yàn)證;
步驟3:服務(wù)器驗(yàn)證通過后,將建立屬于該用戶的session 會(huì)話,將向session 中寫入授權(quán)憑證;
步驟4:服務(wù)器向用戶返回登錄成功的消息,并向用戶所在瀏覽器的cookie 寫入與服務(wù)器一致的sessionid。
鑒權(quán)活動(dòng)的步驟:
步驟5:用戶在登錄后,向服務(wù)器發(fā)起訪問受限資源的請(qǐng)求;
步驟6:服務(wù)器根據(jù)用戶提交的cookie 中sessionid 辨認(rèn)用戶身份,通過之前存放的session 憑證,來驗(yàn)證用戶權(quán)限的合法性;
步驟7:服務(wù)器鑒權(quán)通過后,將用戶需要的受限資源返回給用戶,流程結(jié)束。
可以看到整個(gè)流程中,服務(wù)器要使用session 會(huì)話對(duì)象,只要有新的用戶進(jìn)來,服務(wù)器就要?jiǎng)?chuàng)建新的session 對(duì)象,并且對(duì)通過驗(yàn)證的用戶在session對(duì)象中存入身份憑證,這些數(shù)據(jù)都保存在服務(wù)器內(nèi)存中。整個(gè)服務(wù)器對(duì)session 對(duì)象的管理建立過期機(jī)制,用戶如果長(zhǎng)時(shí)間不訪問服務(wù)器,服務(wù)器將自動(dòng)銷毀該用戶session 對(duì)象,如果用戶一直保持訪問服務(wù)器,服務(wù)器將不斷延長(zhǎng)session 對(duì)象的生命周期。
服務(wù)器要想識(shí)別出不同用戶,依靠的是用戶瀏覽器的cookie 對(duì)象,用戶每次訪問服務(wù)器時(shí),瀏覽器都會(huì)自動(dòng)將客戶端cookie 主動(dòng)提交至服務(wù)器。服務(wù)器通過cookie 中的sessionid 找出用戶,根據(jù)用戶找出對(duì)應(yīng)的憑證,再根據(jù)憑證確定用戶是否具備訪問某資源的合法性。
隨著業(yè)務(wù)流量持續(xù)上升,這些單體架構(gòu)系統(tǒng)需要進(jìn)行集群化部署。使用同一個(gè)域名指向多個(gè)不同服務(wù)器地址,一般可以采用Nginx 反向代理架構(gòu),實(shí)現(xiàn)多臺(tái)服務(wù)器對(duì)外提供服務(wù)。例如一臺(tái)服務(wù)器可以同時(shí)支撐200 個(gè)客戶端進(jìn)行并發(fā)訪問,現(xiàn)在發(fā)現(xiàn)流量高峰時(shí)有600 個(gè)客戶端并發(fā)訪問,則需要3 臺(tái)服務(wù)器對(duì)外提供服務(wù)。集群部署架構(gòu)圖如圖1 所示。
圖1 單體架構(gòu)系統(tǒng)的集群部署
1.3.1 session 同步問題
當(dāng)客戶端對(duì)Nginx 服務(wù)器進(jìn)行訪問時(shí),Nginx服務(wù)器會(huì)將請(qǐng)求轉(zhuǎn)發(fā)至某一臺(tái)服務(wù)器,可以采用輪詢、同一客戶端IP 轉(zhuǎn)發(fā)至同一服務(wù)器、權(quán)重設(shè)置等方式實(shí)現(xiàn)負(fù)載均衡,從而將請(qǐng)求流量分?jǐn)偟讲煌?wù)器,達(dá)到支持大并發(fā)量的目的。根據(jù)session 生成的基本原則,可以看出:當(dāng)用戶訪問服務(wù)時(shí),屬于某一用戶的session 只能保存在某一臺(tái)服務(wù)器上;當(dāng)用戶下次訪問時(shí),就可能切換到其他單體系統(tǒng)的服務(wù)器,當(dāng)前服務(wù)器則沒有屬于該用戶的session 對(duì)象,因?yàn)閷儆谶@個(gè)用戶的session 保存在上一個(gè)服務(wù)器里,把這種問題可以稱之為“session 不同步”。雖然可以通過“同一客戶端IP 轉(zhuǎn)發(fā)至同一服務(wù)器”原則來保證原用戶導(dǎo)向原服務(wù)器,但是一旦這個(gè)服務(wù)器掛起,則又會(huì)出現(xiàn)上述“session 不同步”的問題。
1.3.2 session 存儲(chǔ)問題
當(dāng)用戶量上升時(shí),session 對(duì)象依然存儲(chǔ)在服務(wù)器上,對(duì)服務(wù)器的負(fù)擔(dān)依然沒有降低,如果單臺(tái)服務(wù)器掛起時(shí),session 也隨之消失。
第一類終端表現(xiàn)為傳統(tǒng)APP。從Android Native APP、iOS APP 到考慮各類平臺(tái)快速部署和兼容問題提出的Web APP,再到兼顧Native APP和Web APP 優(yōu)點(diǎn)的混合終端都以各自的方式接入到服務(wù)器系統(tǒng)。
第二類終端表現(xiàn)為依托第三方認(rèn)證的APP。近幾年,隨著微信和支付寶的快速發(fā)展,一類依托微信建立微信公眾號(hào)、小程序和支付寶小程序也廣受企業(yè)用戶歡迎,他們既要與當(dāng)前系統(tǒng)服務(wù)器通信,還要調(diào)取微信和支付寶授權(quán)信息。
第三類終端表現(xiàn)為服務(wù)器API 消費(fèi)者。如提供數(shù)據(jù)作為一種基礎(chǔ)應(yīng)用提供給其他第三方消費(fèi),此類供調(diào)取方消費(fèi)的API 需求也越來越多。大數(shù)據(jù)時(shí)代到來各類數(shù)據(jù)中心積累的數(shù)據(jù)越來越多,應(yīng)充分利用這些數(shù)據(jù)并將數(shù)據(jù)安全保障的對(duì)社會(huì)公開。
以Spring Cloud 技術(shù)體系為例,通常這一架構(gòu)構(gòu)成主要有:注冊(cè)中心集群、配置中心集群、網(wǎng)關(guān)集群、業(yè)務(wù)系統(tǒng)1 集群、業(yè)務(wù)系統(tǒng)2 集群、業(yè)務(wù)系統(tǒng)3集群……。具體如下圖2 所示。
圖2 微服務(wù)通用架構(gòu)
注冊(cè)中心:目前主流的注冊(cè)中心組件有zookeeper、eureka、nacos 等,有了注冊(cè)中心之后,所有的服務(wù)都需要到注冊(cè)中心進(jìn)行登記,所有的服務(wù)調(diào)用都需要通過注冊(cè)中心發(fā)現(xiàn),進(jìn)行統(tǒng)一管理,即使服務(wù)部署在不同的機(jī)器上,注冊(cè)中心也可以進(jìn)行統(tǒng)一管理,且可以很好的實(shí)現(xiàn)負(fù)載均衡。
配置中心:配置是指服務(wù)的統(tǒng)一約定、環(huán)境參數(shù)等信息,建立配置中心的目的是讓這些配置可以統(tǒng)一管理、動(dòng)態(tài)刷新、實(shí)現(xiàn)不同環(huán)境下配置切換,每一個(gè)服務(wù)都可以將自己的配置信息放置到配置中心。配置中心組件有:config-server、nacos 等。
網(wǎng)關(guān):所有的請(qǐng)求都必須通過網(wǎng)關(guān)才可以進(jìn)入,網(wǎng)關(guān)相當(dāng)于客戶端訪問服務(wù)的路由器[2],對(duì)于客戶端來說屏蔽了服務(wù)提供者的內(nèi)部地址,還可以通過網(wǎng)關(guān)的過濾器實(shí)現(xiàn)請(qǐng)求的攔截、實(shí)現(xiàn)響應(yīng)的前置處理和后置處理等[3]。主流的網(wǎng)關(guān)組件有:zuul、Spring Cloud Gateway 等。
業(yè)務(wù)系統(tǒng):一般業(yè)務(wù)系統(tǒng)會(huì)將用戶中心獨(dú)立出來變成一個(gè)基礎(chǔ)子系統(tǒng)獨(dú)立部署,然后再將其他業(yè)務(wù)系統(tǒng)按照微服務(wù)劃分服務(wù)的原則,獨(dú)立出若干個(gè)子系統(tǒng),每個(gè)業(yè)務(wù)子系統(tǒng)都會(huì)注冊(cè)到注冊(cè)中心,都會(huì)通過配置中心拉取配置信息,同時(shí)要想訪問業(yè)務(wù)子系統(tǒng)都會(huì)從網(wǎng)關(guān)接入。
2.3.1 跨域
整個(gè)微服務(wù)架構(gòu)中包含了很多業(yè)務(wù)服務(wù)器,服務(wù)器之間不可能采用相同域名或IP,這就要求對(duì)外提供服務(wù)時(shí),需要支持跨域,而跨域之后cookie 是不能進(jìn)行共享的,也就是服務(wù)A 并不能拿到服務(wù)B的cookie 信息,這也是Web 安全規(guī)則中要求的。
2.3.2 服務(wù)無狀態(tài)
多個(gè)服務(wù)之間相互通信,由于不能共享cookie,就無法記住某一次請(qǐng)求在不同服務(wù)之間是否為同一個(gè)用戶,這些服務(wù)無法保留請(qǐng)求的狀態(tài)信息,或者即使保留了服務(wù)的狀態(tài)也是沒有實(shí)際意義的,因?yàn)檫@些信息不能代表是同一個(gè)用戶。
2.3.3 終端不支持cookie
Native App 以及第三方API 請(qǐng)求的終端并不能支持cookie 機(jī)制,也就是說這些架構(gòu)設(shè)計(jì)開發(fā)的應(yīng)用不能記住服務(wù)器回寫的終端數(shù)據(jù),因此不能記住用戶身份,即使通過OkHttp 等框架可以實(shí)現(xiàn)記住cookie 信息,也會(huì)對(duì)客戶端架構(gòu)設(shè)計(jì)增加很多開發(fā)成本,如果開發(fā)者沒有遵循開發(fā)原則,也會(huì)導(dǎo)致無法識(shí)別用戶身份信息。
Shiro 是權(quán)限管理中非常出色的框架之一,它提供了認(rèn)證、授權(quán)、加密、會(huì)話管理、Web 集成、緩存等功 能 。 Shiro 主 要 包 括 Subject、SecurityManager、Realm 等主要組件,開發(fā)通過實(shí)現(xiàn)自定義的Realm就可以實(shí)現(xiàn)授權(quán)和鑒權(quán)。
shiro 的鑒權(quán)是通過為需要限制訪問的資源方法 添 加 @RequiresUser、@RequiresRoles、@Requires Permission 等注解來實(shí)現(xiàn)的[4]。這些注解本質(zhì)上是通過AOP 來實(shí)現(xiàn)的,通過給這些注解傳遞參數(shù),使用@RequiresUser 表示只允許指定的用戶訪問,使用@RequiresRoles 表示只允許指定的角色訪問,使用@RequiresPermission 只允許擁有某權(quán)限的用戶訪問。當(dāng)請(qǐng)求需要訪問這些受限資源方法時(shí),AOP 就會(huì)來判斷這些限制訪問的資源是否允許訪問。
JWT 是一種基于JSON 的開放標(biāo)準(zhǔn),定義了一套在不同實(shí)體之間傳遞安全信息的數(shù)據(jù)格式標(biāo)準(zhǔn),最終由客戶端向服務(wù)端發(fā)送一個(gè)token(令牌)表示客戶端用戶的身份信息,整個(gè)信息傳輸又是建立在加密算法的基礎(chǔ)上,這樣既保證token 的安全,又能讓服務(wù)器識(shí)別出用戶身份。這個(gè)token 信息包含3個(gè)部分:head 部分(聲明token 類型和加密算法)、payload 部分(數(shù)據(jù)主體:用戶、過期時(shí)間等)、signature 部分(驗(yàn)證數(shù)據(jù)是否被篡改的簽名)。它的優(yōu)點(diǎn)在于不需要依賴cookie 和session,JWT 非常適合微服務(wù)架構(gòu)和不支持cookie 的各類終端。
3.3.1 單體下使用Shiro 和JWT
首先,需要實(shí)現(xiàn)HostAuthenticationToken 接口定義JwtToken 類,用于按照J(rèn)WT 數(shù)據(jù)標(biāo)準(zhǔn)標(biāo)識(shí)用戶身份信息。
其次,定義類JwtFilter 繼承BasicHttpAuthenti cationFilter 類,對(duì)每一個(gè)攜帶jwtToken 的請(qǐng)求進(jìn)行認(rèn)證和授權(quán),只有通過認(rèn)證的才可以授權(quán)放行。
最后,定義JwtRealm 類繼承AuthorizingRealm,實(shí)現(xiàn)認(rèn)證和授權(quán)核心機(jī)制。具體認(rèn)證和授權(quán)流程如圖3 所示。
圖3 單體架構(gòu)下使用shiro 和jwt 實(shí)現(xiàn)認(rèn)證和授權(quán)
步驟1-4:客戶端用戶輸入賬號(hào)和密碼發(fā)起登錄請(qǐng)求,服務(wù)器使用Shiro 框架對(duì)賬號(hào)和密碼進(jìn)行驗(yàn)證,驗(yàn)證通過發(fā)放JwtToken。
步驟5:用戶攜帶合法JwtToken 訪問受限資源,經(jīng)過JwtFilter 過濾器,JwtFilter 對(duì)請(qǐng)求進(jìn)行攔截。
步驟 6:JwtFilter 對(duì) JwtToken 進(jìn)行驗(yàn)證,將JwtToken 傳遞給 Shiro 和 JWT 組件。
步驟 7:Shiro 和 JWT 使用 Realm 對(duì) JwtToken進(jìn)行驗(yàn)證,驗(yàn)證通過返回,并對(duì)用戶進(jìn)行授權(quán)。
步驟8:JwtFilter 放行對(duì)受限資源的請(qǐng)求,轉(zhuǎn)發(fā)請(qǐng)求至受限資源。
步驟9:受限資源的權(quán)限注解被調(diào)用,將使用Shiro 的 isPermitted()方法驗(yàn)證權(quán)限。
步驟10:Shiro 驗(yàn)證權(quán)限通過。
步驟11:返回受限資源到用戶端。
3.3.2 多服務(wù)架構(gòu)改造
基于單體架構(gòu)認(rèn)證和授權(quán)的基本原理,改造成適合微服務(wù)架構(gòu)的認(rèn)證和授權(quán)機(jī)制。首先,創(chuàng)建類JwtGateWayFilter 實(shí)現(xiàn) GatewayFilter 接口,注意與之前單體架構(gòu)實(shí)現(xiàn)的接口不同,GatewayFilter 接口是來自于微服務(wù)架構(gòu)中的網(wǎng)關(guān)組件,目的是對(duì)用戶發(fā)起的請(qǐng)求中JwtToken 信息進(jìn)行驗(yàn)證;其次,繼續(xù)保留JwtToken 類的定義,用于認(rèn)證和授權(quán)過程中token 的傳遞;最后,為每一個(gè)微服務(wù)創(chuàng)建JwtFilter 類繼承BasicHttpAuthenticationFilter 類,創(chuàng)建 JwtRealm 類繼承AuthorizingRealm 類,實(shí)現(xiàn)授權(quán)機(jī)制。
與單體架構(gòu)不同之處:
(1)全局過濾器添加在微服務(wù)的網(wǎng)關(guān)上,用于判斷用戶是否獲得合法認(rèn)證做驗(yàn)證。
(2)每一個(gè)微服務(wù)需要進(jìn)行授權(quán)操作。
(3)每一個(gè)微服務(wù)獨(dú)立實(shí)現(xiàn)鑒權(quán)機(jī)制。
整個(gè)流程圖如圖4 所示。
圖4 微服務(wù)架構(gòu)下使用shiro 和jwt 實(shí)現(xiàn)認(rèn)證和授權(quán)
步驟1-4:登錄通過認(rèn)證,獲得token(JWT)。
步驟5:用戶訪問業(yè)務(wù)資源;
步驟6:網(wǎng)關(guān)使用JwtGatewayFilter 對(duì)token 進(jìn)行驗(yàn)證,如果驗(yàn)證通過放行請(qǐng)求。
步驟7:業(yè)務(wù)集群使用Shiro 和JWT 對(duì)token 進(jìn)行驗(yàn)證并授權(quán),放行請(qǐng)求至受限資源,返回受限資源信息。
步驟8:返回受限資源消息至用戶。
微服務(wù)架構(gòu)下的業(yè)務(wù)系統(tǒng)各自相對(duì)獨(dú)立,每一個(gè)業(yè)務(wù)系統(tǒng)都獨(dú)立部署,各個(gè)服務(wù)之間是處在一種跨域的環(huán)境中,加上客戶端技術(shù)架構(gòu)多樣化,這就要求對(duì)傳統(tǒng)Web 認(rèn)證和授權(quán)機(jī)制進(jìn)行重構(gòu)。既要支持業(yè)務(wù)集群的分布式環(huán)境,又要簡(jiǎn)化認(rèn)證授權(quán)流程,同時(shí)還需要減少服務(wù)器開銷,最終還需要保證Web 服務(wù)的安全性和可靠性。