2015年JWT(JSON Web Token,一種用于身份驗證的令牌格式)剛火起來的時候,技術圈有個說法像咒語一樣被重復:服務器不用存任何東西,驗證簽名就夠了。這個承諾太美了——水平擴展時不用操心會話同步,微服務之間不用共享數據庫,甚至能省掉一整層緩存。
我信了。AuthShield的前兩個版本完全按這個思路設計:簽發令牌、丟掉私鑰、讓下游服務自己驗簽。直到產品經理在需求文檔里寫了兩個字:「退出登錄」。
那一刻我發現,JWT的"無狀態"是個有條件的真理——它只在用戶不主動退出時成立。
退出登錄的物理悖論
JWT的本質是一段帶簽名的JSON。服務器生成它,客戶端保存它,之后每次請求原樣帶回。服務器驗簽通過就放行,整個過程不需要查數據庫。這個設計有個隱含假設:令牌的生命周期完全由過期時間控制,用戶不會提前"殺死"它。
但用戶確實會退出。而且用戶合理期待:點了退出,賬號就該立刻鎖死,哪怕令牌還有59分鐘才過期。
我最初想的方案很天真:把令牌過期時間設短一點,比如5分鐘。但這樣用戶每5分鐘就要重新登錄,體驗災難。OAuth 2.0的解法是把令牌拆成兩個——訪問令牌(Access Token,短期有效)和刷新令牌(Refresh Token,長期有效)。訪問令牌過期后用刷新令牌換新的,既保證安全又減少登錄頻次。
這個方案解決了"頻繁登錄"問題,但沒解決"即時失效"問題。刷新令牌本身也是JWT,如果它被偷了,攻擊者能在用戶退出后繼續刷新訪問令牌,直到刷新令牌自己過期——這可能是一周、一個月,甚至永遠不過期。
Redis黑名單:給無狀態架構打補丁
我最終在AuthShield里加了一層Redis。邏輯很簡單:用戶退出時,把當前訪問令牌的唯一標識(jti,JWT ID)寫進Redis,設置過期時間為令牌的剩余有效期。每次請求來時,先查Redis——如果在黑名單里,直接拒絕。
這行代碼寫進去的時候,我盯著屏幕發了會兒呆。說好的無狀態呢?現在每個請求都要走一趟網絡IO,Redis掛了全站認證崩,多區域部署還要考慮緩存同步。
但產品經理的需求是合理的。安全團隊的要求是合理的。用戶期待自己的退出操作能立刻生效,這更是合理的。唯一不合理的是早期JWT宣傳里那個被過度簡化的"無狀態"神話。
Redis黑名單的方案有個明顯代價:查詢延遲。我測過,本地Redis平均1-2毫秒,跨可用區能到10毫秒以上。對于AuthShield的定位(獨立認證微服務,下游可能幾十個服務調用),這個開銷可以接受。但如果是極高并發的場景,可能需要把黑名單緩存到應用內存,或者用布隆過濾器減少Redis查詢次數。
刷新令牌的旋轉與 reuse 檢測
黑名單只解決了訪問令牌的即時失效。刷新令牌更麻煩——它生命周期長,被偷的后果更嚴重。我的最終方案是"刷新令牌旋轉"(Refresh Token Rotation):每次用刷新令牌換取新的訪問令牌時,同時頒發新的刷新令牌,舊的那個立即作廢。
這個設計有個邊緣情況:如果用戶在兩個設備上同時操作,或者網絡延遲導致舊刷新令牌被重發,新令牌已經生成,舊令牌卻還在路上。這時候需要"重用檢測"(Reuse Detection)——如果服務器收到一個已經作廢的刷新令牌,說明它被偷了,立即吊銷該用戶所有刷新令牌,強制重新登錄。
實現這個邏輯時,我意識到刷新令牌必須是有狀態的。每個刷新令牌需要記錄:用戶ID、設備指紋、簽發時間、是否已旋轉、旋轉后的新令牌ID。這些信息存在PostgreSQL里,和JWT的"無狀態"承諾徹底告別。
但這也是個務實的妥協。完全無狀態的認證只存在于演示代碼里。生產環境要處理設備管理、異常檢測、用戶強制下線、安全事件響應——這些都需要服務器記住點什么。
AuthShield的最終架構
現在的AuthShield有三層狀態:Redis存短期黑名單(訪問令牌),PostgreSQL存長期令牌元數據(刷新令牌),內存緩存熱點黑名單減少Redis壓力。訪問令牌有效期15分鐘,刷新令牌7天,支持配置。
這個架構和2015年那批JWT布道者描述的"服務器無狀態"已經相去甚遠。但它是誠實的——誠實地承認退出登錄是個狀態變更操作,誠實地承認安全需要可撤銷性,誠實地承認用戶控制權比架構純度更重要。
我后來查資料,發現Auth0、Firebase Auth、AWS Cognito這些主流服務都在用類似的混合方案。JWT的無狀態特性被保留在"驗證階段"(任何服務能獨立驗簽),但"生命周期管理"階段引入了狀態層。這不是設計失敗,是問題域本身的復雜度決定的。
如果你正在設計認證系統,我的建議是:先問清楚產品需求里有沒有"退出登錄"和"踢掉某設備"。如果有,直接規劃狀態層,別在"純無狀態"的幻覺里浪費時間。Redis、數據庫、或者專門的令牌管理服務,選一個你能運維的。
JWT仍然是個好工具,但它的賣點需要重新理解——不是"服務器不用存任何東西",而是"驗證時不用存任何東西"。這兩個詞的區別,值一個周末的重構。
你們團隊的退出登錄是怎么實現的?黑名單查Redis還是數據庫,還是干脆不管了等令牌過期?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.