昨天我們總算完成了基礎的註冊功能。
今天要來實作登入功能,因為使用到 JWT,本節會先介紹 JWT 的概念與特色,再以循序圖介紹各元件如何進行溝通。礙於篇幅,會把實作細節留在明天。
JWT (JSON Web Token) 與 SessionId 是兩種主流的 Token 機制,他們最大的差異在於「登入資訊的儲存位置」:
Token 存放位置的選擇最主要的考量之一是「伺服器是否有水平擴展的需求?」若有,則需要伺服器保持無狀態(Stateless)。這種情況下,多數系統會選擇以 JWT 進行實作,因為即便將 SessionId 存在資料庫,當用戶數量到達一定程度,資料庫也會遇上負載或儲存空間的困擾。以 JWT Token 實作減少了這種認證時對共享儲存的查詢負擔(因為後端不需儲存Token)。
除此之外,兩種做法也各有其他優缺點,整理如下:
鑒於本次架構是以微服務的概念進行拆分,當前登入機制主流的作法多以保持伺服器無狀態為原則,因此本次會以 JWT 進行登入 token 的實作,以下稍微介紹一下 JWT 的組成與注意事項。
JWT組成
JWT Token 本身自帶用戶資訊,其由Header、Payload、Signature組成:
下方是在 jwt.io 中預設的案例,可以看到右側為 Header 與 Payload,左側則為按照這些資訊組成的 JWT Token,Header與 Payload 資訊會以 Baes64 編碼轉換:
圖片來源:jwt.io
JWT Token 應注意的安全原則
AuthController
:接收並處理 HTTP 請求。AuthService
:處理業務邏輯,例如註冊。UserDetailsServiceImpl
:從資料庫加載使用者資訊供 Spring Security 使用。UserEntity
:代表資料庫中的使用者資料,實作 UserDetails
介面。SecurityConfig
:配置 Spring Security 的安全策略。JwtUtils
負責 JWT Token 的生成與驗證。LoginRequest
:用戶端傳來的帳號密碼(登入請求的 DTO)。LoginResponse
:回傳給用戶端的 JWT Token 和使用者資訊 (登入回應 DTO)。POST /users/login
),請求 Body 中包含 Email 和密碼。Controller
接收到請求,並將 Email 和密碼打包成一個 UsernamePasswordAuthenticationToken
物件。Controller
呼叫 AuthenticationManager
的 authenticate()
方法,將 Token 傳入,啟動 Spring Security 認證流程。AuthenticationManager
將任務委派給內部的 DaoAuthenticationProvider
。Provider
呼叫我們實作的 UserDetailsServiceImpl
的 loadUserByUsername()
方法,去資料庫查找使用者。UserDetailsServiceImpl
透過 UserRepository
查詢資料庫,若找到使用者,則將 UserEntity
物件回傳。若找不到,則拋出 UsernameNotFoundException
。Provider
收到 UserEntity
後,使用我們在 SecurityConfig
中定義的 PasswordEncoder
,比對請求傳入的密碼雜湊後和資料庫中的密碼是否相符。AuthenticationManager
會回傳一個完全認證過的 Authentication
物件給 Controller
。AuthenticationManager
會拋出 BadCredentialsException
。Controller
收到認證成功的 Authentication
物件後,呼叫 JwtUtils
來生成 JWT Token。Controller
將 JWT Token 和其他必要的使用者資訊,打包成 LoginResponse
DTO 回傳給客戶端。