Web App Token 鑒權方案的設計與思考

作為一種新興的鑒權方案,Token 作為 Session ID 的替代品有許多天然優勢,很多主流產品也從 Session ID 鑒權變成了 Token 鑒權。在我近期完成的產品實踐中,也使用了 Token 進行鑒權。作為鑒權方案設計的參與者,我今天與負責該部分業務的同學進行了一些討論,同時咨詢了幾位業界老司機,結合我在產品設計之初的一些調研,總結出這篇文章分享給大家。

我不是安全專家,只是在能力范圍內對系統安全盡可能地做了優化,考慮不周的地方歡迎大家批評指正。

關于用戶系統

對于一個簡單的用戶系統(不考慮復雜的權限控制,只考慮最單一的“合法用戶”的鑒定),其功能其實可以被拆的很簡單:注冊、登錄、鑒權。

  • 注冊:用戶將用戶名和密碼交給服務器,并由服務器存儲的過程。
  • 登錄:用戶將用戶名和密碼交給服務器,服務器鑒定是否正確的過程(在 Token鑒權系統中,這一步如果通過,會生成并返回 Token)。
  • 鑒權:用戶將 Token 發送給服務器,服務器校驗該 Token 是否合法的過程(不考慮復雜鑒權)。

安全問題

流程清楚了,我們就來分析一下問題。不考慮前端可能出現的網絡抓包等問題,僅從服務器角度考慮,我們可能遇到的安全問題有以下幾個:

  • 密碼泄漏
  • 生成 Token 的 Secret Key(Salt)泄漏
  • Token 泄漏 / 偽造

歸納一下:我們要解決的最重要的安全問題,就是用戶最機密的安全信息被泄漏或偽造。

我們的鑒權設計實踐

在我最近完成的產品上,為了規避這些問題,我們在關鍵步驟上進行了一些處理。整個鑒權系統依賴 Apache Shiro 框架;同時,在密碼處理,Token 認證上,我們結合了一些自己的解決方案。

整個流程大致是這樣的:(流程圖軟件到期了 TAT)

注冊

Register

登錄

Login

鑒權

Authentication

關于密碼加密存儲與驗證

密碼是一定要進行加密存儲的。用戶系統最核心的數據表,就是包含用戶名(ID)、加密后的密碼、Salt 的表。Salt 的生成,我們使用了 Shiro 提供的隨機字符串生成工具,與用戶名連接后,再進行 MD5。然后使用 Salt 加密密碼,然后同時保存 Salt 和加密后的密碼。
當用戶登錄時,我們使用 Salt 對用戶輸入的密碼進行加密,再嘗試與存儲的密碼進行匹配。

關于 Token 方案(JWT Token)

我們使用 JWT Token 作為我們的 Token 方案。

JWT Token

JWT Token 的全稱是 JSON Web Token。一個 JWT Token 由三部分構成:Header,Payload,Signature。Header 規定了 Token 使用的加密方式與 Token 的類型,Payload 是 Token 中包含的用戶信息(用戶名,過期時間等),Signature 是 Header 的 Base64 值 + Payload 的 Base64 值 + Secret Key 生成的字符串,再對該字符串使用 Header 中規定的散列方式(HS256 或 RS256)取散列值后得到的字符串。一個典型的 JWT Token 是這個樣子的:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
// HEADER
{
  "alg": "HS256",
  "typ": "JWT"
}
// PAYLOAD
{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
// SIGNATURE
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),  
  secret
) 

Header、Payload 和 Signature 用 . 分隔。

驗證 Token 的時候,我們只需要將前兩段(即 Header 和 Payload 的 Base64)加上 Secret Key,然后按照 Header 規定的加密方式進行加密,將生成的字符串與第三段(Signature)比對即可。當然,Token 驗證的實踐上,不同的項目存在一些分歧:有些人會將生成的 Token 直接存在數據庫(比如 Redis)里,然后通過 Query 的方式驗證是否合法。這一點我們隨后討論。

一個 JWT Token 唯一不可見的部分,就是 Secret Key。它是保證這個 Token 合法且安全的唯一字段。拿不到 Secret Key ,就無法生成 Token,也無法驗證 Token。這種 Token 機制很常見(HTTPS 的握手過程就類似這樣,SSH 連接也是 - 私鑰只有一方持有),難點在于,如何生成并管理 Secret Key。

Secret Key

首先,所有用戶使用相同的 Secret Key 一定是不合理的。所以我們要解決的第一個問題是,如何為每一個用戶生成唯一的 Secret Key ?

還記得剛才的 Salt 么?每個用戶的 Salt 都是唯一的,我們使用 Salt ,但不直接使用 Salt 作為 Secret Key。我們使用 Salt + 加密后的密碼,再取 MD5 值作為該用戶的 Secret Key。每次鑒權前,我們通過這個方式生成 Secret Key,再使用 Secret Key 進行鑒權。

安全性分析

整套系統的安全之處在于,我們沒有將任何敏感信息本地化。假設一種最壞的情況:攻擊者拿到了我們數據庫的全部數據,他能做什么?

  • 獲取密碼:密碼被加密了,而且每個用戶使用不同的 Salt 進行加密,加密方法是自定義的,不知道加密方法的話難以破解。
  • 獲取 Token:我們沒有保存任何的 Token。
  • 獲取 Secret Key:Secret Key 是算出來的,即便拿到了 Salt,不知道算法也無法直接得到 Secret Key。

我們避免了直接保存任何安全信息。攻擊者拿到的數據,都無法被直接利用。即便嘗試破解,代價也是巨大的。

關于 Redis

在我看到的一些實踐中,有些項目喜歡使用 Redis 存儲生成的 Token,從而簡化鑒權流程,提升鑒權效率。這樣做可以嗎?

我咨詢了一位業界專家,同時查閱了相關資料,我給出的答案是:可以,但是不合理,不推薦。

避免用 Redis 直接存儲 Token

還記得我們安全性分析的前提么:如果攻擊者拿到了我們數據庫的全部數據,他能做什么?

將 Token 保存在 Redis 中,一定是有風險的。如果服務器被攻破,用戶 Token 泄漏的話,在規定的過期時間內,這些被泄漏的 Token 將會使用戶賬戶變得非常危險。

當然,如果系統運行在內網環境,或者系統本身對用戶安全的要求不高,這種方案從某種程度上講,確實可以提升鑒權效率,簡化鑒權流程。但是鑒于其可能存在的安全問題,?我不推薦。

可以用 Redis 緩存 Salt

在我們的產品設計中,我們使用 Salt 計算 Secret Key,然后再進行 Token 認證。我們可以在用戶登錄時把 Salt 緩存到 Redis 中以提升查詢效率。

進一步優化

使用 Payload 生成 Secret Key

現在,整個系統的安全性基本可靠了。但是,仔細分析系統的設計,還是有一點問題:每次鑒權都需要去查詢 Salt,I/O 開銷比較大。這恰恰也是有些人使用 Redis 的原因之一 —— 提升查詢速度。

仔細分析一下,我們用 Salt 當做了生成 Secret Key 的 Seed ,目的在于保證 Secret Key 唯一,同時不直接存儲 Secret Key 。但其實,保持 Secret Key 唯一的方式有很多,不一定要通過 Salt 。實際上只有登錄操作必須依賴 Salt,鑒權操作完全可以使用別的機制。

我們可以使用 JWT 的 Payload 中的某些字段,通過特定算法生成 Secret Key。比如:有效期時間戳 + 用戶名,再取 SHA256 散列值(當然可以更復雜,不過要注意性能開銷)。因為生成 Secret Key 的算法是不透明的,所以 Secret Key 也是相對安全的。

如果對把生成 Token 的信息放在 Payload 中心存顧慮的話,我們可以在服務器上通過靜態配置文件的方式設置固定的 Secret Salt ,配合 Payload 生成 Secret Key。

通過這樣的方式,我們可以避免在鑒權階段對數據庫進行訪問,提升響應效率。我們也可以利用 Secret Salt 進行細粒度的權限角色劃分,在此就不贅述了。

更標準的密碼加密模式

關于密碼加密等方式,我的老師給了我一些建議:可以使用 Blowfish 算法進行對稱加密。這樣的加密更標準,更安全。

JWT Token 與前端

JWT Token 應該放在哪

官方建議使用 Bearer 的模式,即:

Authorization: Bearer <token>

合理使用 Payload,避免 Token 過長

JWT Token 是有 Payload 的,這從一定程度上會造成 Payload 濫用。我在 Chrome 上遇到一個奇怪的 Bug:如果 Authorization 過長,Chrome 傳遞這個字段的時候會發生截斷。我們的產品剛開始研發的時候,過度依賴 JWT 的 Payload 傳遞用戶基本信息(用戶名、所屬用戶組、郵箱等),造成 Token 長度非常長。后來對 Token 進行了幾次瘦身,才避免了 Chrome 上的 Bug。

前端真的需要依賴 Payload 嗎?

答案是否定的。前端并不關心,也不應該關心 Token 的 Payload 是什么,真正使用 Payload 的應該是后端。前端獲取用戶信息的方式,應當是在用戶登錄的時候,由服務器作為 HTTP Response 回傳,并使用 Cookie / Local Storage / Session Storage 進行持久化存儲,而不是通過解析 Token 的 Payload 獲得。

以上就是我對 Web App Token 鑒權方案的一些思考。以下是有關 Web App Token 認證的文檔:

RFC 6749: The OAuth 2.0 Authorization Framework
RFC 6750: The OAuth 2.0 Authorization Framework: Bearer Token Usage
RFC 7519 JSON Web Token (JWT)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容