JWT原理分析
JWT 認證完整鏈路——從登錄到退出的每一步都有一個真實項目在跑我做過一個政務系統(tǒng)的認證模塊Java 從零實現(xiàn)了一套 JWT 認證——雙 Token、黑名單、Cookie 和 Header 雙通道提取、用戶信息緩存、權限鑒權。這篇文章拆開這個模塊的完整源碼從 Token 結構到每條請求走過的每一步每一步都給出來源和原因。文章目錄JWT 認證完整鏈路——從登錄到退出的每一步都有一個真實項目在跑一、為什么不用 Session二、Token 結構——三段 Base64 拼起來的不只是一串字三、生成 Token——兩個方法兩種生命周期四、refreshToken 換發(fā)——Token 時效的權衡五、驗證 Token——一行 parse 背后的三步六、完整請求鏈路——一條請求從進來到出去的 7 步七、黑名單——無狀態(tài)的代價和補救八、Token 的兩個通道——Header 和 Cookie 各自的用途九、認證和鑒權是兩件事十、配置十一、結語一、為什么不用 SessionSession 是服務端狀態(tài)。用戶登錄后服務端在內(nèi)存或 Redis 里存一份session_id → 用戶信息的映射瀏覽器通過 Cookie 回傳 session_id。這個模型在單服務器時代夠用。但政務系統(tǒng)有幾個特點多節(jié)點部署——如果走 Session要么做 sticky session綁定到固定節(jié)點那臺掛了全掉要么做 session 共享引入 Redis 集中存儲單點故障風險Cookie 跨域限制——Cookie 的 domain 綁定了一個域名。如果系統(tǒng)有多個子域名app1.gov.cn、app2.gov.cn同域名下的 Cookie 不能跨子域共享——而 Session 依賴 Cookie前后端分離——前端可能是獨立的 Vue/React 應用跟后端不同域。Session 的 Cookie 在同源策略下傳不到后端JWT 解決了這三個問題Token 是自包含的——用戶信息簽名后放在 Token 里服務端不存狀態(tài)。任何節(jié)點拿到 Token 用同一個密鑰驗簽就能還原用戶身份。Token 通過 Authorization Header 傳不受 Cookie 同源策略限制。代價Token 一旦簽發(fā)就無法主動失效不額外存儲狀態(tài)的話。這個代價引出了接下來的雙 Token 黑名單設計。二、Token 結構——三段 Base64 拼起來的不只是一串字一個 JWT Token 長這樣eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9.HN_sWbXG-8_zpHjz5-TGKl06VfnKnCpN6A9TknR8VFs用句號拆開是三段Header → eyJhbGciOiJIUzI1NiJ9 Payload → eyJzdWIiOiJhZG1pbiJ9 Sig → HN_sWbXG-8_zpHjz5-TGKl06VfnKnCpN6A9TknR8VFsBase64 解碼前兩段Header{alg:HS256,typ:JWT}Payload{sub:admin,psnId:admin,psnName:管理員,unitId:U001,type:access,iss:browise,iat:1781683991,exp:1781691191}Signature 的計算方式HMAC-SHA256( Base64(Header) . Base64(Payload), secret )關鍵理解Payload 是明文 Base64任何人都能解出來看。Token 的安全性不靠加密——靠簽名。沒有 secret 的人可以讀到 Payload 里的psnIdadmin——但他改不了。他如果把psnId改成superadmin然后重算 Base64——簽名就對不上了——服務端一驗就拒絕。所以 JWT 的設計哲學是Payload 可以公開看但不能改。三、生成 Token——兩個方法兩種生命周期登錄成功后生成兩個 Token。accessToken2 小時// 來源: JwtUtil.javapublicStringgenerateAccessToken(StringpsnId,StringpsnName,StringunitId){MapString,ObjectclaimsnewHashMap();claims.put(psnId,psnId);claims.put(psnName,psnName);claims.put(unitId,unitId);claims.put(type,access);// 標記類型returnJwts.builder().setClaims(claims).setSubject(psnId).setIssuer(browise).setIssuedAt(newDate()).setExpiration(newDate(System.currentTimeMillis()7200000))// 2小時.signWith(SignatureAlgorithm.HS256,secret).compact();}refreshToken7 天publicStringgenerateRefreshToken(StringpsnId){MapString,ObjectclaimsnewHashMap();claims.put(psnId,psnId);claims.put(type,refresh);// 標記類型——和 access 區(qū)分returnJwts.builder().setClaims(claims).setSubject(psnId).setIssuer(browise).setIssuedAt(newDate()).setExpiration(newDate(System.currentTimeMillis()604800000))// 7天.signWith(SignatureAlgorithm.HS256,secret).compact();}兩個 Token 的區(qū)別不只是過期時間——type字段在驗證時用來區(qū)分typerefresh的 Token 不能當 accessToken 用——它只能用來換新 Token。如果你把 refreshToken 放進 Authorization Header 調業(yè)務接口——Filter 層檢查 type 不是 “access” 直接拒絕。四、refreshToken 換發(fā)——Token 時效的權衡為什么需要兩個 TokenaccessToken 只有 2 小時因為它是高頻傳輸?shù)摹看握埱蠖紟е?。如?accessToken 有效期太長比如 7 天一旦泄露攻擊者有 7 天時間可以任意調用接口——而 JWT 本身不存儲狀態(tài)你沒法主動讓它失效。但 2 小時太短——用戶在一天之內(nèi)可能被強制登出好幾次體驗極差。所以用一個 7 天的 refreshToken 來解決accessToken 過期后前端拿 refreshToken 去/auth/refresh換一個新的 accessToken——不用重新登錄。accessToken 過期2小時 │ ├─ 前端請求 /api/xxx → 401 │ ├─ 前端攔截 401拿 refreshToken 調 /auth/refresh │ ├─ 驗證 refreshToken 簽名 有效期 │ ├─ 檢查 refreshToken 是否在黑名單logout 時加入 │ ├─ 通過 → 生成新的 accessToken 返回 │ └─ 不通過 → 前端跳轉登錄頁 │ └─ 前端拿到新 accessToken → 重試原請求refreshToken 是 httpOnly 的 Cookie——JS 不能讀。這樣即使前端被 XSS 攻擊拿到了 accessToken攻擊者也只能用 2 小時。要長期控制需要拿到刷新能力——但 refreshToken 在 httpOnly Cookie 里XSS 拿不到。五、驗證 Token——一行 parse 背后的三步// 來源: JwtUtil.javapublicClaimsparseToken(Stringtoken){returnJwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();}parseClaimsJws背后做了三件事用 secret 重算簽名——和 Token 里的 Signature 段比對。不匹配 → 拋出SignatureException——Token 被篡改過檢查 exp——當前時間 exp → 拋出ExpiredJwtException——Token 已過期解析 Payload——驗證通過后返回Claims對象——也就是 Payload 里的所有字段三步在一行代碼里完成。如果任何一步失敗——Filter 直接返回 401。六、完整請求鏈路——一條請求從進來到出去的 7 步JwtAuthFilter 攔截所有請求除了登錄和公開接口。每條請求走 7 步HTTP 請求到達 │ ├─ ① 提取 Token │ ├─ Authorization: Bearer xxx → 截取 Bearer 之后的部分 │ └─ Cookie: BROWISE_ATxxx → 從 Cookie 取值備用通道 │ ├─ ② 驗證 Token │ ├─ parseToken(token) → 簽名校驗 過期檢查 │ └─ 解析 Claims → 拿到 psnId, type, iat │ ├─ ③ 黑名單檢查jwtBlacklist │ ├─ isInvalid(psnId, iat) → 該用戶此時刻之前的 Token 全失效 │ └─ isInvalidByToken(token) → 精確失效logout 單設備 │ ├─ ④ 加載用戶信息userProfileCache │ ├─ 查 OP_PERSON → psnName, unitId, unitName │ ├─ 查 SYS_USER.STATUS → 賬號是否啟用 │ ├─ 查 SYS_USER_ROLE → 角色列表 │ └─ 緩存 1 小時TTL 內(nèi)直接返回不查庫 │ ├─ ⑤ 注入當前用戶CurrentUser.set(profile) │ └─ ThreadLocal → 請求線程內(nèi)全局可訪問 │ ├─ ⑥ 賬號狀態(tài)檢查 │ └─ !profile.isEnabled() → 403 賬號已禁用 │ └─ ⑦ 權限檢查menuAuthProvider.canAccess(path) ├─ 路徑在 allowIdList 白名單 → 跳過鑒權 └─ 路徑需匹配用戶角色的菜單權限 → 不匹配返回 403第三步的黑名單是最容易被忽略的設計——它解決了Token 已經(jīng)簽發(fā)了但需要讓它失效的問題。七、黑名單——無狀態(tài)的代價和補救JWT 不存狀態(tài)。但業(yè)務上需要主動失效——用戶修改密碼、管理員禁用賬號、用戶主動登出——這些場景要求已經(jīng)簽發(fā)的 Token 立刻不能用了。這個系統(tǒng)里黑名單分兩層第一層用戶級失效按簽發(fā)時間當用戶修改密碼或管理員重置密碼時——把這個用戶的(psnId, 當前時間戳)寫入黑名單。此后驗證邏輯這樣判斷publicbooleanisInvalid(StringpsnId,longiat){LongcutoffTimeblacklist.get(psnId);// 從 Map 取該用戶的最早失效時間if(cutoffTimenull)returnfalse;// 不在黑名單 → 正常returniatcutoffTime;// Token 簽發(fā)時間 ≤ 失效時間 → 已失效}如果用戶在 12:00 改了密碼——寫入blacklist[admin] 12:00。之后所有iat ≤ 12:00的 Token 全部作廢——不管你是手機登錄的還是瀏覽器登錄的。因為改了密碼意味著所有歷史登錄都不可信。第二層Token 級失效登出單設備用戶點擊退出登錄——把當前這個 Token 寫入精確黑名單publicbooleanisInvalidByToken(Stringtoken){returntokenBlacklist.contains(token);}logout 只失效當前設備——手機端的 Token 不受影響。黑名單的存儲選擇在演示環(huán)境里黑名單是內(nèi)存 Map——服務重啟后黑名單清空。生產(chǎn)環(huán)境換成 Redis——設置 key 的 TTL 等于 Token 的剩余有效時間——Token 過期后 key 自動刪除不用手動清理。八、Token 的兩個通道——Header 和 Cookie 各自的用途Token 的傳輸用了兩個通道通道Token 類型場景為什么Authorization HeaderaccessTokenAJAX 請求不受 Cookie 同源限制跨域可用httpOnly CookieaccessToken頁面跳轉、form 提交瀏覽器自動攜帶不用前端手動加httpOnly CookierefreshToken刷新接口JS 不可讀——XSS 攻擊拿不到兩個通道的原因是——header 方式需要前端在每個請求里手動加Authorization: Bearer xxx。但有些場景比如表單 POST 提交、a標簽跳轉前端控制不了 header。這時候 Cookie 作為備用通道——瀏覽器自動附在請求上——Filter 層兩個地方都查有任意一個就繼續(xù)驗證。九、認證和鑒權是兩件事很多剛接觸權限系統(tǒng)的開發(fā)者把認證和鑒權混為一談。這個模塊里它們是兩個獨立的步驟認證Authentication 你是誰 → ①②③④⑤提取 Token → 驗簽 → 黑名單 → 加載用戶 → 注入上下文 鑒權Authorization 你能做什么 → ⑦拿到用戶角色和當前請求路徑 → 匹配 → 放行或拒絕為什么分開因為有些接口不需要鑒權——只要你是登錄用戶就可以訪問比如查看自己的個人信息。有些接口只需要認證不需要角色判斷——比如全員可見的公告。十、配置browise:auth:enabled:truejwt:secret:browise-demo-local-dev-secret-key-must-be-32charsaccess-token-expire-ms:7200000# 2小時refresh-token-expire-ms:604800000# 7天issuer:browiseexclude-paths:-/auth/**-/captcha-/loginsecret在生產(chǎn)環(huán)境不應該寫在配置文件里。放在環(huán)境變量或密鑰管理服務中注入——${JWT_SECRET}。十一、結語JWT 認證不是一個引入一個庫、寫兩行代碼的事情。它的核心設計決策——雙 Token、黑名單、雙通道傳輸、認證鑒權分離——都是在回答同一個問題“不存狀態(tài)的 Token 怎么安全地用”accessToken 短有效防止泄露窗口、refreshToken 長有效避免頻繁登錄、httpOnly Cookie 防 XSS、黑名單補上無狀態(tài)的漏洞、用戶緩存省掉每次查庫——這五個設計合在一起才是一個生產(chǎn)可用的 JWT 認證模塊。? 亮點從為什么不用 Session的自然動機出發(fā)按照請求鏈路逐層拆開 JWT 認證的完整實現(xiàn)——雙 Token 生命周期、黑名單兩級失效、Header/Cookie 雙通道傳輸、認證鑒權分離——每層都給出了真實源碼和為什么選這個設計的原因。適合正在實現(xiàn)或重構認證模塊的開發(fā)者也適合面試中你們系統(tǒng)怎么做登錄的的系統(tǒng)級回答。擴展方向OAuth2 授權碼模式在政務系統(tǒng)中的應用、多系統(tǒng)單點登錄SSO的 Token 共享方案、JWT Redis 組合實現(xiàn)有狀態(tài) Token 的折中方案。

相關新聞

大廠面試,自進化 agent 正在成為主流!

大廠面試,自進化 agent 正在成為主流!

最近社區(qū)學員反饋一些Agent 面經(jīng)時,發(fā)現(xiàn)自進化 agent正在成為主流!今天從一道字節(jié)算法二面的題開始,帶你看懂大廠真正想要什么樣的人才能力。 👔 面試官:“human feedback 是怎么被 agent 消化吸收的?” …

2026/7/30 0:51:10 閱讀更多
微信小程序畢業(yè)設計選題指南與30個創(chuàng)新案例

微信小程序畢業(yè)設計選題指南與30個創(chuàng)新案例

1. 微信小程序畢業(yè)設計選題的價值與趨勢在當今移動互聯(lián)網(wǎng)時代,微信小程序已成為連接用戶與服務的重要橋梁。根據(jù)最新統(tǒng)計,微信小程序日活躍用戶已突破4億,覆蓋200多個細分行業(yè)。對于計算機相關專業(yè)的畢業(yè)生而言,選擇微信小程序作為…

2026/7/30 1:41:42 閱讀更多
芯片時序簽核實戰(zhàn):從STA原理到PrimeTime約束與調試

芯片時序簽核實戰(zhàn):從STA原理到PrimeTime約束與調試

1. 從靜態(tài)時序分析到PrimeTime:為什么我們需要它?如果你做過數(shù)字芯片設計,不管是前端RTL編碼還是后端物理實現(xiàn),肯定都聽過“時序收斂”這個詞。簡單來說,就是確保芯片里的所有信號,都能在時鐘規(guī)定的“節(jié)拍”…

2026/7/30 1:41:42 閱讀更多
SQL Server 2008 安裝與 Java JDBC 連接實戰(zhàn):從環(huán)境搭建到排錯指南

SQL Server 2008 安裝與 Java JDBC 連接實戰(zhàn):從環(huán)境搭建到排錯指南

1. 項目概述:從零搭建一個可用的數(shù)據(jù)訪問層 最近在整理一個遺留的老項目,發(fā)現(xiàn)其核心數(shù)據(jù)存儲依然依賴 SQL Server 2008,而應用層則是用 Java 寫的。為了后續(xù)的維護和可能的遷移驗證,我需要在一臺干凈的機器上重新搭建這套環(huán)境。這…

2026/7/30 1:41:42 閱讀更多
??粕鶤I論文助手千筆智能體功能解析與使用測評

專科生AI論文助手千筆智能體功能解析與使用測評

1. 項目背景與核心價值作為一名在學術工具領域深耕多年的研究者,我最近測試了一款名為"千筆專業(yè)學術智能體"的AI論文輔助平臺。這個專門面向??粕后w的學術工具,在當前AI寫作助手泛濫的市場中顯得尤為特別。與市面上大多數(shù)通用型寫作助手不同…

2026/7/30 1:41:42 閱讀更多
哪些關系型數(shù)據(jù)庫支持向量檢索?分布式數(shù)據(jù)庫與 AI 應用選型解析 —— 阿里云 PolarDB-X

哪些關系型數(shù)據(jù)庫支持向量檢索?分布式數(shù)據(jù)庫與 AI 應用選型解析 —— 阿里云 PolarDB-X

向量檢索正在成為關系型數(shù)據(jù)庫支撐 AI 應用的重要演進方向。所謂"關系型數(shù)據(jù)庫支持向量",是指數(shù)據(jù)庫在原有結構化數(shù)據(jù)能力之上,能夠存儲與檢索由大模型生成的高維向量(embedding),從而支撐相似度檢索、語義搜…

2026/7/30 1:21:13 閱讀更多
[GESP202606 四級] 掃雷

[GESP202606 四級] 掃雷

B4557 [GESP202606 四級] 掃雷 https://www.luogu.com.cn/problem/B4557 中國計算機學會(CCF)2026年6月C四級講解——掃雷 https://www.bilibili.com/video/BV1MCMg6AEXR/ B4557 [GESP202606 四級] 掃雷 https://www.bilibili.com/video/BV1ZKTj6ZEVh/ 2…

2026/7/30 0:01:06 閱讀更多