Writing / 009

登入系統設計:從密碼 Hash 到 JWT、Session

登入系統設計:從密碼 Hash 到 JWT、Session

登入系統不只是帳號密碼比對。本文從最簡單的登入流程出發,拆解密碼 Hash、Session ID、JWT、雙 Token、2FA 與 Rate Limit 各自解決的問題。

前言

在一支系統設計的影片留言區,我看到一個有趣的問題:「你會怎麼設計一個登入系統,像 Google 那樣的?」.

這個問題讓我愣住了,因為我自己做登入系統時,其實都只是把網路上教的、AI 建議的,一股腦塞到登入系統中,從來沒有認真想過,這些東西為什麼要存在?

所以這篇會從最簡單的版本出發,拆解 Hash、Session、JWT 與 2FA 背後真正要解決的問題。

最簡單的版本:帳號 + 密碼

設計一個登入系統,最直覺的起點就是,使用者輸入帳號和密碼,伺服器從資料庫撈出對應的資料,比對是否一致,一致就讓你進去。

這個概念非常簡單,甚至自己寫一個小網站,大概幾十行程式碼就能做到。資料庫的設計也很基本,一張 User 表,裡面存使用者id、帳號、密碼等等欄位。

但是,這個「最簡單的版本」藏著一個致命問題,就是你把密碼原文存進資料庫了。

如果資料庫今天被駭客入侵,攻擊者可以直接拿到所有人的密碼。更慘的是,很多使用者都習慣用同一組密碼,一旦洩露,後果不只是這個網站的問題,而是全面性的帳號淪陷。

第一個問題:密碼不能明文存

所以面對這個問題,解法的核心思路是,就算駭客拿到了資料庫,也不能直接看到密碼原文。

這時候「雜湊(Hash)」就是答案。做法是在密碼存進資料庫之前,先經過一個單向的數學運算,把它轉換成一串看起來無意義的字元,伺服器存的是這串字元,而不是密碼本身。

重點在於「單向」,你可以從密碼推算出雜湊值,但無法從雜湊值反推原始密碼。例如,使用者登入時,系統會把你輸入的密碼重新雜湊一次,再和資料庫裡的雜湊值比對,吻合就放行。

這樣做之後,就算資料庫被偷,駭客看到的也只是一堆雜湊值,沒辦法直接用來登入。

第二個問題:伺服器怎麼記得你是你?

密碼的問題解決了,但還有另一個關鍵問題,就是你剛剛登入成功了,伺服器要怎麼在下一次請求時,知道這還是同一個人?

這邊的問題出在 HTTP 是無狀態的。無狀態是指使用者每一次請求(request)對伺服器來說都是獨立的,伺服器不會因為你上一秒才登入成功,下一秒就自動記得你是誰。

補充:HTTP 協定是目前伺服器和瀏覽器溝通的標準方式,例如:呼叫 API、下載圖片、送出表單,基本上都靠它。

所以為了讓伺服器能夠在登入後記住使用者,我們還需要一套可以證明自己身份的「憑證策略」。

目前主流的做法有兩種,分別是 JWTSession ID

它們的差別可以用一個比喻來理解:

  • JWT 像是一張有效期的票券,伺服器發給你之後就不管了,票沒過期你就能用。
  • Session ID 像是一張記名的門禁卡,伺服器在自己的資料庫裡記著你這張卡,隨時可以停用。

兩種做法各有優缺點。JWT 讓伺服器比較省力,不需要額外存資料,但一旦出問題,要撤銷一張已發出的票非常麻煩。Session ID 讓伺服器掌握控制權,出狀況可以立刻作廢,但每次請求都要查資料庫,效能上會有一點代價。

沒有哪一種做法是絕對正確的,重點還是看你的系統更在意什麼,是擴展性,還是控制力。

第三個問題:憑證被偷怎麼辦?

有了憑證之後,新的問題出現了,如果這張「票」或「門禁卡」被偷走,駭客不就可以冒充你了嗎?

是的,確實是這樣子,如果「票」或「門禁卡」被偷走,基本上駭客就可以冒用你的身份向伺服器發出請求。

為了降低這種風險,Session IDJWT 在設計上採取了不同的應對方式。

(1) Session ID

Session ID 的防護邏輯比較直接,伺服器在資料庫層控制每個 Session 的狀態,一旦 Session ID 被偷走,隨時可以讓它失效。

具體做法有兩個。第一,使用者持續活躍就延長有效期,閒置超過一定時間就自動作廢。第二,設定最長有效時間,超過就強制登出。這套機制在出問題時,可以立刻讓特定帳號的所有 Session 失效。

(2) JWT 雙 Token

JWT 的常見做法是使用雙 Token 機制,也就是同時發出兩個 Token:

  • Access Token(短效):有效期很短,通常 15 分鐘,每次請求都帶著它,伺服器用它來確認你的身份。
  • Refresh Token(長效):有效期較長,例如 7 天,平時不使用,只有在 Access Token 過期時,才拿它來換一個新的 Access Token。

這樣設計的好處是,就算 Access Token 被偷走,駭客能利用的時間也很有限,通常最多只有十幾分鐘。至於 Refresh Token,通常會存放在 Cookie 中,也應該以雜湊方式記錄在資料庫裡,以降低洩露風險。

不過,傳統 JWT 的問題在於 Refresh Token 不容易追蹤與撤銷。因此,實務上常會借用 Session ID 的概念,把 Refresh Token 也記錄在資料庫中,一旦發現異常,就可以立即將它作廢。

登入系統還需要哪些安全防護

走到這裡,一個基本的登入系統已經成形了,但安全這件事沒有終點,以下幾個方向是現代系統常見的延伸。

(1) 二因子驗證(2FA)

2FA 的特色是,就算密碼被盜,駭客還需要通過第二道驗證才能進入帳號。最常見的做法是 TOTP(基於時間的一次性密碼),也就是 Google Authenticator 上那串每 30 秒更新一次的數字。此外,系統通常也會提供備用碼(Backup Code),讓你在手機遺失時還能進入帳號,避免被自己鎖在門外。

補充:2FA 的做法很多種,TOTP 只是其中一種而已,其他還有 SMS 驗證碼、Email 驗證、生物辨識等等。

(2) 速率限制(Rate Limit)

如果不設限登入次數,駭客可以無限次嘗試不同的密碼組合,這叫做「暴力破解」。速率限制的做法是,同一個 IP 在短時間內嘗試太多次,就封鎖它一段時間。這是最直接的防禦方式,通常也會搭配 Redis 來快速記錄請求次數,避免每次都查主資料庫。

(3) 第三方登入

「用 Google 帳號登入」這類功能,背後是 OAuth 協定。使用者不需要在你的網站建立新帳號,直接授權 Google 把基本資料傳給你。好處是你不需要管理密碼,安全責任部分轉移給 Google 這類大平台,也大幅降低使用者的登入摩擦。

結論

這題目最有趣的地方在於,每一個技術決策的背後,都是一個被發現的問題。密碼要雜湊,是因為明文有洩露風險;需要憑證策略,是因為 HTTP 無狀態;雙 Token 存在,是因為憑證本身也可能被盜。複雜度不是設計師故意堆上去的,而是現實的攻擊面一點一點逼出來的。

如果你也在學系統設計,以筆者的觀察,比起死背技術清單,更有價值的練習是,從最簡單的版本開始,自己問自己「這樣會有什麼問題?」然後找出對應的解法。這個思考習慣,比記住任何一個技術名詞都更有用。

本次分享就到這邊,希望你喜歡這次的內容! 如果有任何關於登入系統設計的想法,歡迎留言跟我們一起討論! 我是 Pablo,我們下次見👋

— end — 寫於 2026.03.25