昨天我們總算完成了基礎的註冊功能。
今天要來實作登入功能,因為使用到 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 回傳給客戶端。