自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊微服務(wù)架構(gòu)中的用戶認(rèn)證方案

開發(fā) 架構(gòu)
今天主要涉及三方面內(nèi)容,首先咱們回顧了基于 Session 的有狀態(tài)用戶認(rèn)證解決方案,其次介紹了 JWT 與 JJWT 的使用,最后講解了利用 JWT 實現(xiàn)微服務(wù)架構(gòu)認(rèn)證的兩種方案,對產(chǎn)生的新問題也進(jìn)行了梳理。

今天來聊聊微服務(wù)中一個重要的話題:如何設(shè)計微服務(wù)架構(gòu)下的用戶認(rèn)證方案。今天主要涉及三個方面的內(nèi)容:

  • 傳統(tǒng)的用戶認(rèn)證方案;
  • JWT 與 JJWT;
  • 基于網(wǎng)關(guān)的統(tǒng)一用戶認(rèn)證。

傳統(tǒng)的用戶認(rèn)證方案

我們直奔主題,什么是用戶認(rèn)證呢?對于大多數(shù)與用戶相關(guān)的操作,軟件系統(tǒng)首先要確認(rèn)用戶的身份,因此會提供一個用戶登錄功能。用戶輸入用戶名、密碼等信息,后臺系統(tǒng)對其進(jìn)行校驗的操作就是用戶認(rèn)證。用戶認(rèn)證的形式有多種,最常見的有輸入用戶名密碼、手機驗證碼、人臉識別、指紋識別等,但其目的都是為了確認(rèn)用戶的身份并與之提供服務(wù)。

圖片

用戶認(rèn)證

在傳統(tǒng)的單體單點應(yīng)用時代,我們會開發(fā)用戶認(rèn)證的服務(wù)類,從登錄界面提交的用戶名密碼等信息通過用戶認(rèn)證類進(jìn)行校驗,然后獲取該用戶對象將其保存在 Tomcat 的 Session 中,如下所示:

圖片

單點應(yīng)用認(rèn)證方案

隨著系統(tǒng)流量的增高,單點應(yīng)用以無法支撐業(yè)務(wù)運行,應(yīng)用出現(xiàn)高延遲、宕機等狀況,此時很多公司會將應(yīng)用改為 Nginx 軟負(fù)載集群,通過水平擴展提高系統(tǒng)的性能,于是應(yīng)用架構(gòu)就變成了這個樣子。

圖片

Java Web 應(yīng)用集群

雖然改造后系統(tǒng)性能顯著提高,但你發(fā)現(xiàn)了么,因為之前用戶登錄的會話數(shù)據(jù)都保存在本地,當(dāng) Nginx 將請求轉(zhuǎn)發(fā)到其他節(jié)點后,因為其他節(jié)點沒有此會話數(shù)據(jù),系統(tǒng)就會認(rèn)為沒有登錄過,請求的業(yè)務(wù)就會被拒絕。從使用者的角度會變成一刷新頁面后,系統(tǒng)就讓我重新登錄,這個使用體驗非常糟糕。

我們來分析下,這個問題的根本原因在于利用 Session 本地保存用戶數(shù)據(jù)會讓 Java Web 應(yīng)用變成有狀態(tài)的,在集群環(huán)境下必須保證每一個 Tomcat 節(jié)點的會話狀態(tài)一致的才不會出問題。因此基于 Redis 的分布式會話存儲方案應(yīng)運而生,在原有架構(gòu)后端增加 Redis 服務(wù)器,將用戶會話統(tǒng)一轉(zhuǎn)存至 Redis 中,因為該會話數(shù)據(jù)是集中存儲的,所以不會出現(xiàn)數(shù)據(jù)一致性的問題。

圖片

Redis 統(tǒng)一存儲用戶會話

但是,傳統(tǒng)方案在互聯(lián)網(wǎng)環(huán)境下就會遇到瓶頸,Redis 充當(dāng)了會話數(shù)據(jù)源,這也意味著 Redis 承擔(dān)了所有的外部壓力,在互聯(lián)網(wǎng)數(shù)以億計的龐大用戶群規(guī)模下,如果出現(xiàn)突發(fā)流量洪峰,Redis 能否經(jīng)受考驗就會成為系統(tǒng)的關(guān)鍵風(fēng)險,稍有差池系統(tǒng)就會崩潰。

那如何解決呢?其實還有一種巧妙的設(shè)計,在用戶認(rèn)證成功,后用戶數(shù)據(jù)不再存儲在后端,而改為在客戶端存儲,客戶端每一次發(fā)送請求時附帶用戶數(shù)據(jù)到 Web 應(yīng)用端,Java 應(yīng)用讀取用戶數(shù)據(jù)進(jìn)行業(yè)務(wù)處理,因為用戶數(shù)據(jù)分散存儲在客戶端中,因此并不會對后端產(chǎn)生額外的負(fù)擔(dān),此時認(rèn)證架構(gòu)會變成下面的情況。

圖片

客戶端存儲用戶信息

當(dāng)用戶認(rèn)證成功后,在客戶端的 Cookie、LocalStorage 會持有當(dāng)前用戶數(shù)據(jù),在 Tomcat 接收到請求后便可獲取用戶數(shù)據(jù)進(jìn)行業(yè)務(wù)處理。但細(xì)心的你肯定也發(fā)現(xiàn),用戶的敏感數(shù)據(jù)是未經(jīng)過加密的,在存儲與傳輸過程中隨時都有泄密的風(fēng)險,決不能使用明文,必須要對其進(jìn)行加密。

那如何進(jìn)行加密處理呢?當(dāng)然,你可以自己寫加解密類,但更通用的做法是使用 JWT 這種標(biāo)準(zhǔn)的加密方案進(jìn)行數(shù)據(jù)存儲與傳輸。

Json Web Token(JWT)介紹

無論是微服務(wù)架構(gòu),還是前后端分離應(yīng)用,在客戶端存儲并加密數(shù)據(jù)時有一個通用的方案:Json Web Token(JWT),JWT是一個經(jīng)過加密的,包含用戶信息的且具有時效性的固定格式字符串。下面這是一個標(biāo)準(zhǔn)的JWT字符串。

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9.NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU

這段加密字符串由三部分組成,中間由點“.”分隔,具體含義如下。

  • 第一部分 標(biāo)頭(Header):標(biāo)頭通常由兩部分組成:令牌的類型(即 JWT)和所使用的簽名算法,例如 HMAC SHA256 或 RSA,下面是標(biāo)頭的原文:
{  
"alg": "HS256",
"typ": "JWT"
}

然后,此 JSON 被 Base64 編碼以形成 JWT 的第一部分。

eyJhbGciOiJIUzI1NiJ9
  • 第二部分 載荷(Payload):載荷就是實際的用戶數(shù)據(jù)以及其他自定義數(shù)據(jù)。載荷原文如下所示。
{  
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

然后對原文進(jìn)行 Base64 編碼形成 JWT 的第二部分。

eyJzdWIiOiJ7XCJ1c2VySWRcIjoyLFwidXNlcm5hbWVcIjpcImxpc2lcIixcIm5hbWVcIjpcIuadjuWbm1wiLFwiZ3JhZGVcIjpcInZpcFwifSJ9
  • 第三部分 簽名(Sign):簽名就是通過前面兩部分標(biāo)頭+載荷+私鑰再配合指定的算法,生成用于校驗 JWT 是否有效的特殊字符串,簽名的生成規(guī)則如下。
HMACSHA256(base64UrlEncode(header) + "." +  base64UrlEncode(payload),  secret)

生成的簽名字符串為:

NT8QBdoK4S-PbnhS0msJAqL0FG2aruvlsBSyG226HiU

將以上三部分通過“.”連接在一起,就是 JWT 的標(biāo)準(zhǔn)格式了。

JWT 的創(chuàng)建與校驗

此時,你肯定有疑問 JWT 是如何生成的,又是如何完成有效性校驗?zāi)??因?JWT 的格式與算法是固定的,在 Java 就有非常多的優(yōu)秀開源項目幫我們實現(xiàn)了JWT 的創(chuàng)建與驗簽,其中最具代表性的產(chǎn)品就是 JJWT。JJWT 是一個提供端到端的 JWT 創(chuàng)建和驗證的 Java 庫,它的官網(wǎng)是:https://github.com/jwtk/jjwt,有興趣的話你可以到官網(wǎng)閱讀它的源碼。

JJWT 的使用是非常簡單的,下面我們用代碼進(jìn)行說明,關(guān)鍵代碼我已做好注釋。

  • 第一步,pom.xml 引入 JJWT 的 Maven 依賴。
<dependency>  
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
  • 第二步,編寫創(chuàng)建 JWT 的測試用例,模擬真實環(huán)境 UserID 為 123 號的用戶登錄后的 JWT 生成過程。
@SpringBootTest  
public class JwtTestor {
/**
* 創(chuàng)建Token
*/
@Test
public void createJwt(){
//私鑰字符串
String key = "1234567890_1234567890_1234567890";
//1.對秘鑰做BASE64編碼
String base64 = new BASE64Encoder().encode(key.getBytes());
//2.生成秘鑰對象,會根據(jù)base64長度自動選擇相應(yīng)的 HMAC 算法
SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());
//3.利用JJWT生成Token
String data = "{\"userId\":123}"; //載荷數(shù)據(jù)
String jwt = Jwts.builder().setSubject(data).signWith(secretKey).compact();
System.out.println(jwt);
}
}

運行結(jié)果產(chǎn)生 JWT 字符串如下:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw
  • 第三步,驗簽代碼,從 JWT 中提取 123 號用戶數(shù)據(jù)。這里要保證 JWT 字符串、key 私鑰與生成時保持一致。否則就會拋出驗簽失敗 JwtException。
/**  
* 校驗及提取JWT數(shù)據(jù)
*/
@Test
public void checkJwt(){
String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxMjN9In0.1p_VTN46sukRJTYFxUg93CmfR3nJZRBm99ZK0e3d9Hw";
//私鑰
String key = "1234567890_1234567890_1234567890";
//1.對秘鑰做BASE64編碼
String base64 = new BASE64Encoder().encode(key.getBytes());
//2.生成秘鑰對象,會根據(jù)base64長度自動選擇相應(yīng)的 HMAC 算法
SecretKey secretKey = Keys.hmacShaKeyFor(base64.getBytes());
//3.驗證Token
try {
//生成JWT解析器
JwtParser parser = Jwts.parserBuilder().setSigningKey(secretKey).build();
//解析JWT
Jws<Claims> claimsJws = parser.parseClaimsJws(jwt);
//得到載荷中的用戶數(shù)據(jù)
String subject = claimsJws.getBody().getSubject();
System.out.println(subject);
}catch (JwtException e){
//所有關(guān)于Jwt校驗的異常都繼承自JwtException
System.out.println("Jwt校驗失敗");
e.printStackTrace();
}
}

運行結(jié)果如下:

{"userId":123}

以上便是 JWT 的生成與校驗代碼,你會發(fā)現(xiàn)在加解密過程中,服務(wù)器私鑰 key 是保障 JWT 安全的命脈。對于這個私鑰在生產(chǎn)環(huán)境它不能寫死在代碼中,而是加密后保存在 Nacos 配置中心統(tǒng)一存儲,同時定期更換私鑰以防止關(guān)鍵信息泄露。

講到這應(yīng)該你已掌握 JWT 的基本用法,但是在微服務(wù)架構(gòu)下又該如何設(shè)計用戶認(rèn)證體系呢?

基于網(wǎng)關(guān)的統(tǒng)一用戶認(rèn)證

關(guān)于網(wǎng)關(guān)統(tǒng)一用戶認(rèn)證和鑒權(quán)可以看陳某之前的文章:實戰(zhàn)干貨!Spring Cloud Gateway 整合 OAuth2.0 實現(xiàn)分布式統(tǒng)一認(rèn)證授權(quán)!

下面我們結(jié)合場景講解 JWT 在微服務(wù)架構(gòu)下的認(rèn)證過程。這里我將介紹兩種方案:

  • 服務(wù)端自主驗簽方案;
  • API 網(wǎng)關(guān)統(tǒng)一驗簽方案。

服務(wù)端自主驗簽方案

首先咱們來看服務(wù)端驗簽的架構(gòu)圖。

圖片

服務(wù)端自主驗簽方案

首先梳理下執(zhí)行流程:

  • 第一步,認(rèn)證中心微服務(wù)負(fù)責(zé)用戶認(rèn)證任務(wù),在啟動時從 Nacos 配置中心抽取 JWT 加密用私鑰;
  • 第二步,用戶在登錄頁輸入用戶名密碼,客戶端向認(rèn)證中心服務(wù)發(fā)起認(rèn)證請求:
http://usercenter/login #認(rèn)證中心用戶認(rèn)證(登錄)地址
  • 第三步,認(rèn)證中心服務(wù)根據(jù)輸入在用戶數(shù)據(jù)庫中進(jìn)行認(rèn)證校驗,如果校驗成功則返回認(rèn)證中心將生成用戶的JSON數(shù)據(jù)并創(chuàng)建對應(yīng)的 JWT 返回給客戶端,下面是認(rèn)證中心返回的數(shù)據(jù)樣本;
{  
"code": "0",
"message": "success",
"data": {
"user": {
"userId": 1,
"username": "zhangsan",
},
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ7XCJ1c2VySWRcIjoxLFwidXNlcm5hbWVcIjpcInpoYW5nc2FuXCIsXCJuYW1lXCI6XCLlvKDkuIlcIixcImdyYWRlXCI6XCJub3JtYWxcIn0ifQ.1HtfszarTxLrqPktDkzArTEc4ah5VO7QaOOJqmSeXEM"
}
}
  • 第四步,在收到上述 JSON 數(shù)據(jù)后,客戶端將其中 token 數(shù)據(jù)保存在 cookie 或者本地緩存中;
  • 第五步,隨后客戶端向具體某個微服務(wù)發(fā)起新的請求,這個 JWT 都會附加在請求頭或者 cookie 中發(fā)往 API 網(wǎng)關(guān),網(wǎng)關(guān)根據(jù)路由規(guī)則將請求與jwt數(shù)據(jù)轉(zhuǎn)發(fā)至具體的微服務(wù)。中間過程網(wǎng)關(guān)不對 JWT 做任何處理;
  • 第六步,微服務(wù)接收到請求后,發(fā)現(xiàn)請求附帶 JWT 數(shù)據(jù),于是將 JWT 再次轉(zhuǎn)發(fā)給用戶認(rèn)證服務(wù),此時用戶認(rèn)證服務(wù)對 JWT 進(jìn)行驗簽,驗簽成功提取其中用戶編號,查詢用戶認(rèn)證與授權(quán)的詳細(xì)數(shù)據(jù),數(shù)據(jù)結(jié)構(gòu)如下所示:
{  
"code": "0",
"message": "success",
"data": {
"user": { #用戶詳細(xì)數(shù)據(jù)
"userId": 1,
"username": "zhangsan",
"name": "張三",
"grade": "normal"
"age": 18,
"idno" : 130.......,
...
},
"authorization":{ #權(quán)限數(shù)據(jù)
"role" : "admin",
"permissions" : [{"addUser","delUser","..."}]
}
}
}
  • 第七步,具體的微服務(wù)收到上述 JSON 后,對當(dāng)前執(zhí)行的操作進(jìn)行判斷,檢查是否擁有執(zhí)行權(quán)限,權(quán)限檢查通過執(zhí)行業(yè)務(wù)代碼,權(quán)限檢查失敗返回錯誤響應(yīng)。

到此從登錄創(chuàng)建 JWT 到驗簽后執(zhí)行業(yè)務(wù)代碼的完整流程已經(jīng)完成。

下面咱們來聊一聊第二種方案:

API 網(wǎng)關(guān)統(tǒng)一驗簽方案

圖片

API 網(wǎng)關(guān)統(tǒng)一驗簽方案

API 網(wǎng)關(guān)統(tǒng)一驗簽與服務(wù)端驗簽最大的區(qū)別是在 API 網(wǎng)關(guān)層面就發(fā)起 JWT 的驗簽請求,之后路由過程中附加的是從認(rèn)證中心返回的用戶與權(quán)限數(shù)據(jù),其他的操作步驟與方案一是完全相同的。

在這你可能又會有疑惑,為什么要設(shè)計兩種不同的方案呢?其實這對應(yīng)了不同的應(yīng)用場景:

服務(wù)端驗簽的時機是在業(yè)務(wù)代碼執(zhí)行前,控制的粒度更細(xì)。比如微服務(wù) A 提供了“商品查詢”與“創(chuàng)建訂單”兩個功能,前者不需要登錄用戶就可以使用,因此不需要向認(rèn)證中心額外發(fā)起驗簽工作;而后者是登錄后的功能,因此必須驗簽后才可執(zhí)行。因為服務(wù)端驗簽是方法層面上的,所以可以精確控制方法是否驗簽。但也有不足,正是因為驗簽是在方法前執(zhí)行,所以需要在所有業(yè)務(wù)方法上聲明是否需要額外驗簽,盡管這個工作可以通過 Spring AOP+注解的方式無侵入實現(xiàn),但這也無疑需要程序員額外關(guān)注,分散了開發(fā)業(yè)務(wù)的精力。

相應(yīng)的,服務(wù)端驗簽的缺點反而成為 API 網(wǎng)關(guān)驗簽的優(yōu)勢。API 網(wǎng)關(guān)不關(guān)心后端的服務(wù)邏輯,只要請求附帶 JWT,就自動向認(rèn)證中心進(jìn)行驗簽。這種簡單粗暴的策略確實讓模塊耦合有所降低,處理起來也更簡單,但也帶來了性能問題,因為只要請求包含 JWT 就會產(chǎn)生認(rèn)證中心的遠(yuǎn)程通信。如果前端工程師沒有對 JWT 進(jìn)行精確控制,很可能帶來大量多余的認(rèn)證操作,系統(tǒng)性能肯定會受到影響。

那在項目中到底如何選擇呢?服務(wù)端驗簽控制力度更細(xì),適合應(yīng)用在低延遲、高并發(fā)的應(yīng)用,例如導(dǎo)航、實時交易系統(tǒng)、軍事應(yīng)用。而 API 統(tǒng)一網(wǎng)關(guān)則更適合用在傳統(tǒng)的企業(yè)應(yīng)用,可以讓程序員專心開發(fā)業(yè)務(wù)邏輯,同時程序也更容易維護(hù)。

全新的挑戰(zhàn)

雖然 JWT 看似很美,在實施落地過程中也會遇到一些特有的問題,例如:

WT 生成后失效期是固定的,很多業(yè)務(wù)中需要客戶端在不改變 JWT 的前提下,實現(xiàn) JWT 的“續(xù)簽”功能,但這單靠 JWT 自身特性是無法做到的,因為 JWT 的設(shè)計本身就不允許生成完全相同的字符串。為了解決這個問題,很多項目在生成的 JWT 設(shè)為“永久生效”,架構(gòu)師利用 Redis 的 Expire 過期特性在后端控制 JWT 的時效性。這么做雖然讓 JWT 本身變得有狀態(tài),但這可能也是在各種權(quán)衡后的“最優(yōu)解”。類似的,例如:強制 JWT 立即失效、動態(tài) JWT 有效期都可以使用這個辦法解決。

圖片

某個 JWT 在 3600 秒后過期

對于上面兩種認(rèn)證方案,還有優(yōu)化的空間,比如在服務(wù)A第一次對某個 JWT 進(jìn)行驗簽后獲取用戶與權(quán)限數(shù)據(jù),那在 JWT 的有效期內(nèi)便可將數(shù)據(jù)在本地內(nèi)存或者 Redis 中進(jìn)行緩存,這樣下一次同樣的 JWT 訪問時直接從緩存中提取即可,可以節(jié)省大量服務(wù)間通信時間。但引入緩存后你也要時刻關(guān)注緩存與用戶數(shù)據(jù)的一致性問題,是要性能還是要數(shù)據(jù)可靠,這又是一個架構(gòu)師需要面對的抉擇。

小結(jié)

今天主要涉及三方面內(nèi)容,首先咱們回顧了基于 Session 的有狀態(tài)用戶認(rèn)證解決方案,其次介紹了 JWT 與 JJWT 的使用,最后講解了利用 JWT 實現(xiàn)微服務(wù)架構(gòu)認(rèn)證的兩種方案,對產(chǎn)生的新問題也進(jìn)行了梳理。

在多年的架構(gòu)生涯中,我自己也在不斷感慨,架構(gòu)是一門取舍的藝術(shù),沒有完美的架構(gòu),只有適合的場景,希望未來同學(xué)們可以多學(xué)習(xí)一些前沿技術(shù),興許隨著技術(shù)發(fā)展沒準(zhǔn)魚和熊掌真的可以兼得呢。

責(zé)任編輯:武曉燕 來源: 碼猿技術(shù)專欄
相關(guān)推薦

2024-07-31 09:09:20

2022-11-02 08:31:53

BFF架構(gòu)App

2024-12-27 10:12:28

2022-08-04 08:46:16

單體架構(gòu)微服務(wù)事務(wù)管理

2021-02-07 09:05:56

微服務(wù)結(jié)構(gòu)云原生

2023-01-29 09:06:24

微服務(wù)劃分關(guān)聯(lián)

2023-09-05 08:53:51

2025-03-11 10:58:00

2023-12-15 09:57:13

微服務(wù)鏈路服務(wù)

2022-11-08 08:35:53

架構(gòu)微服務(wù)移動

2021-07-20 08:03:43

微服務(wù)應(yīng)用程序

2018-12-06 14:56:46

微服務(wù)隔離熔斷

2019-09-29 10:29:02

緩存模式微服務(wù)架構(gòu)

2018-05-09 08:18:26

微服務(wù)改造架構(gòu)

2023-07-28 09:23:24

微服務(wù)架構(gòu)

2020-11-26 18:18:21

微服務(wù)業(yè)務(wù)規(guī)模技術(shù)

2024-07-01 12:09:12

2025-01-08 09:23:03

2022-12-31 14:51:48

微服務(wù)Golang

2025-02-10 02:20:00

微服務(wù)SOA架構(gòu)
點贊
收藏

51CTO技術(shù)棧公眾號