1. 什么是HTTP請求頭 (Request Headers)?
當你的瀏覽器或手機App向服務器發起一個HTTP請求時,這個請求并不僅僅包含你要訪問的URL(比如?/logout)和可能的數據(請求體),它還附帶了一堆“元數據(Metadata)”,這些元數據就是請求頭。
請求頭是一些鍵值對(Key-Value pairs),它們向服務器提供了關于本次請求的各種上下文信息,比如:
Host:?api.example.com?(我想訪問哪個服務器)
User-Agent:?Mozilla/5.0 ...?(我是用什么瀏覽器或設備發起的請求)
Accept:?application/json?(我希望你返回給我JSON格式的數據)
Content-Type:?application/json?(如果我發送了數據,那么這些數據的格式是JSON)
Authorization:?Bearer eyJhbGciOiJIUzI1Ni...?(這是身份憑證)
2. 為什么需要?Authorization?請求頭?
HTTP協議本身是無狀態的(Stateless)。這意味著服務器默認情況下,每一次收到請求,都把它當作一個全新的、陌生的請求來對待。它不會記得你上一次是誰,或者你是否已經登錄過。
為了解決這個問題,我們需要一種機制來讓客戶端在每次請求時都“自報家門”。Authorization?請求頭就是目前最主流的實現方式,特別是配合?Token-based Authentication(基于令牌的認證)
Token認證機制具有安全性。
核心憑證只傳一次: 用戶的賬號密碼只在登錄那一次通過HTTPS安全地傳輸到服務器。
暴露的是“臨時工”: Token是一個臨時的、可控的憑證。它本質上是服務器對“我信任這個客戶端”的一次授權聲明。即使Token在傳輸過程中被截獲,它的危害也是有限的:
有時效性: Token通常被設置為在幾小時或幾天后自動過期。攻擊者拿到一個過期的Token是完全沒用的。
可被吊銷: 服務器可以建立一個“黑名單”機制。一旦發現某個Token泄露,可以立刻將其加入黑名單,使其立即失效。
權限可控: Token的Payload(載荷)部分可以包含用戶的角色和權限信息。服務器可以簽發一個權限受限的Token(例如,一個只讀Token),即使泄露,攻擊者也無法執行寫操作。
3. 典型的登錄認證流程
登錄: 用戶提交用戶名和密碼到一個登錄接口(如?/login)。
獲取令牌 (Token): 服務器驗證用戶名和密碼成功后,生成一個加密的、有時效性的字符串,這個字符串就是令牌(Token)。服務器會將這個Token返回給客戶端。
客戶端存儲令牌: 客戶端(瀏覽器或App)收到Token后,會將其安全地存儲起來(比如在瀏覽器的?localStorage?或?sessionStorage?中)。
后續請求攜帶令牌: 從此以后,客戶端向服務器發起的每一個需要認證的請求,都必須在?Authorization?請求頭中附帶上這個Token。通常,Token前面還會加上一個Bearer的前綴,表示“持有者認證”。
服務器驗證令牌: 服務器收到請求后,會先檢查?Authorization?請求頭。如果存在,它會取出Token,對其進行解密和驗證(檢查簽名是否正確、是否過期等)。驗證通過后,服務器就知道了“哦,原來是張三發來的請求”,然后才繼續處理業務邏輯。
4.?Token的生命周期
“生成”階段:?每一次獨立的登錄(Login)行為,都會觸發服務器生成一個全新的、不一樣的Token。
“使用”階段: 在某一次登錄成功后,直到這個Token過期或用戶手動登出之前,客戶端在發起每一次業務請求(Request)時,都會重復使用這同一個Token。
5.JWT的“三段式”結構?
JWT Token而是由?三個部分?通過?.?連接而成的:
Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第一部分:Header (頭部)?eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
內容: 描述這個JWT元信息的一個JSON對象,經過Base64Url編碼而成。
解碼后:?{"alg": "HS256", "typ": "JWT"}
含義:
alg: "HS256" (HMAC using SHA-256),聲明了簽名部分(Signature)使用的加密算法。
typ: "JWT",聲明了這是一個JWT。
第二部分:Payload (載荷)?eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
Payload?的設計初衷是用來存放與“用戶身份和權限”相關的、相對穩定的信息,這些信息在用戶的一次登錄會話中是不會改變的。
內容: 包含了我們想傳遞的實際業務數據的一個JSON對象,也經過Base64Url編碼。
解碼后:?{"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
含義 (這里是一些標準字段):
sub: Subject (主題),通常存放用戶的唯一ID。—— 這就是“判斷這個Token是誰的”關鍵!
name: 用戶名。
iat: Issued At (簽發時間),是一個時間戳。
exp: Expiration Time (過期時間),是一個時間戳。—— 這就是“檢查是否過期”的關鍵!
注意: 任何人都可以對Header和Payload進行Base64Url解碼,看到里面的內容。所以,絕對不能在Payload中存放敏感信息,如密碼!
第三部分:Signature (簽名)?SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
內容: 這是一個加密后的字符串,是JWT安全性的核心。
生成過程: 它是通過以下公式,使用在**服務器端秘密保存的一個密鑰(Secret Key)**生成的:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secretKey )含義: 這個簽名就像是這份“聲明文件”(Header + Payload)的“數字指紋”或“防偽印章”。
6. 服務器驗證Token的全過程?
假設服務器收到一個HTTP請求,請求頭里有?Authorization: Bearer [一個JWT字符串]。在若依這類框架中,通常會有一個過濾器(Filter),比如?JwtAuthenticationTokenFilter,它會在請求到達你的Controller之前,自動執行以下驗證流程:
第1步:檢查請求頭,取出Token
這步很簡單,就是代碼層面的字符串操作。
第2步:解密和驗證(核心步驟)
這一步通常會由一個專門的JWT工具類(比如使用?jjwt?庫)來完成。validateToken(jwtToken)?方法內部會做以下事情:
a. 拆分Token
將收到的?jwtToken?字符串,按?.?分割成三部分:headerPart,?payloadPart,?signaturePart。
b. 驗證簽名(Signature)—— 最關鍵的安全校驗
服務器端有一個只有自己知道的、絕對保密的?secretKey。這個密鑰在項目啟動時就加載到內存中了。
服務器會完全忽略收到的?signaturePart。
它會用收到的?headerPart?和?payloadPart,以及自己保存在內存中的?secretKey,重新計算一次簽名。
newSignature = HMACSHA256(headerPart + "." + payloadPart, mySecretKey)然后,它將?newSignature?與收到的?signaturePart?進行逐位比較。
如果兩者完全一致: 這證明了這個Token確實是由我(或擁有相同密鑰的其他可信服務)簽發的,并且?Header?和?Payload?在傳輸過程中沒有被任何人篡改過。因為只有持有?secretKey?才能生成出正確的簽名。——?驗證通過!
如果兩者不一致: 這說明Token要么是偽造的,要么內容被篡改了。——?驗證失敗,立即拒絕請求!
c. 驗證載荷(Payload)—— 業務規則校驗
在簽名驗證通過后,服務器才會信任?Payload?里的內容。
它會對?payloadPart?進行Base64Url解碼,得到一個JSON對象。
然后,它會檢查Payload中的標準聲明(Claims):
檢查過期時間 (exp):?if (currentTime > payload.getExp()) { ... }
獲取exp字段的值(一個時間戳)。
與當前服務器時間進行比較。
如果當前時間已經超過了exp時間,說明Token已過期。——?驗證失敗,拒絕請求!
還可以檢查其他聲明,比如nbf?(Not Before,生效時間)等。
第3步:判斷這個Token是誰的,并建立會話上下文
在簽名和有效期都驗證通過后,現在服務器可以完全信任這個Token了。
a. 提取用戶信息
服務器會從已經解碼的?Payload?中,提取出用戶的唯一標識,通常是?sub?(Subject) 字段。
String userId = payload.get("sub");它還可能提取出角色、權限等其他信息。
List<String> roles = payload.get("roles");
b. 建立安全上下文 (Security Context)
現在服務器知道了:“這個合法請求是用戶?userId?發來的,他擁有?roles?這些角色。”
框架(如Spring Security)會創建一個認證對象(Authentication),把這些用戶信息封裝進去。
然后,將這個認證對象存入一個**與當前線程綁定的“安全上下文”**中(SecurityContextHolder)。
c. 放行請求
過濾器的工作到此完成,它會將請求放行,繼續傳遞給后續的過濾器,最終到達Controller方法。當請求最終到達Controller時,不再需要關心Token了。只需要從那個“安全上下文”中獲取用戶信息即可。?