到目前為止,任何人都能呼叫寫入類的 API。本章加上安全機制: 使用 Spring Security 搭配 JWT,實作註冊、登入、 認證(Authentication)與角色授權(Authorization)。
認證與授權:先分清楚兩件事
P5.6cmP5.6cm 認證 Authentication & 授權 Authorization
回答「你是誰?」 & 回答「你能做什麼?」
驗證帳號密碼、驗證 token & 檢查角色/權限是否足夠
失敗回 401 Unauthorized & 失敗回 403 Forbidden
範例:沒帶 token 就想新增書籍 & 範例:一般使用者想刪除書籍
401:伺服器「不知道你是誰」——沒帶 token、token 過期或無效。
403:伺服器「知道你是誰,但你不夠格」——已登入, 但角色權限不足以執行這個操作。
本專案的權限設計:
lP7.4cm 身分 & 可執行的操作
訪客(未登入) & 查詢書籍(所有 GET)
& 查詢+新增、修改書籍(POST/PUT/PATCH)
& 全部操作,包含刪除書籍(DELETE)
為什麼用 Token 而不是 Session?
P5.6cmP5.6cm Session(有狀態) & JWT(無狀態)
登入狀態存在「伺服器」記憶體 & 登入狀態存在「token 本身」
伺服器要記住每個使用者 & 伺服器完全不必記,驗簽章即可
多台伺服器需共享 Session(黏著或 Redis) & 任何一台伺服器都能獨立驗證
依賴 Cookie,跨網域、App 較麻煩 & 放在 Header,網頁/App/第三方通用
REST 講究無狀態,JWT 正好讓「每個請求自帶完整的身分證明」, 這也是它成為 API 認證主流的原因。
JWT 結構解剖
JWT(JSON Web Token)是三段 Base64Url 編碼的字串,用「」串接:
Payload 中的常見聲明(Claims):
llP6.2cm 聲明 & 全名 & 意義
& Subject & 主體——這個 token 屬於誰(本專案放帳號)
& Issued At & 簽發時間
& Expiration Time & 過期時間(過期的 token 驗證會失敗)
& (自訂聲明) & 本專案自訂:使用者角色
Base64 人人都能解開——把 token 貼到 https://jwt.io 就能看到完整內容。 因此 Payload 絕對不能放密碼、卡號等敏感資料。 JWT 的安全性來自簽章:內容被改動一個字元,簽章驗證就會失敗, 而沒有伺服器密鑰的人偽造不出合法簽章。
認證流程全景圖
階段二的第 6 步之前,請求會先通過我們的 (驗證簽章與有效期), 再通過授權規則(檢查角色)——接下來就逐一實作這些元件。
實作 1:JwtService——產生與驗證 token
密鑰放在 的 , 並支援用環境變數 覆寫。 HS256 要求密鑰至少 32 字元;正式環境務必改用隨機產生的長密鑰, 且只透過環境變數或密鑰管理服務提供,絕不進版本控制。
實作 2:CustomUserDetailsService——載入使用者
Spring Security 不關心使用者存在哪裡,它只認 介面。 我們實作它,從 MySQL 載入使用者:
實作 3:JwtAuthenticationFilter——每個請求的守門員
過濾器對「沒帶 token」的請求也是放行的——它只負責 「有合法 token 就標記為已登入」。 真正決定能不能存取的是下一關的授權規則: 匿名請求存取受保護的 API 時,會在那裡被擋下並回 401。 這樣設計讓公開 API(如查詢書籍)與受保護 API 能共用同一條過濾器鏈。
實作 4:SecurityConfig——規則總部
授權規則逐條解讀(由上而下比對,先中先贏):
P6.6cmP4.8cm 規則 & 效果
\(\to\) & 註冊、登入完全公開
\(\to\) & 查詢書籍開放訪客
\(\to\) & 刪除僅限管理員
\(\to\) & 其餘寫入需登入
\(\to\) & 沒列到的一律要登入
除了上表的「URL 規則」,也可以用「方法層級」授權—— 在 Controller 方法上標 (需在設定類別啟用 )。 本專案的刪除 API 兩種都示範了:URL 規則當第一道防線, 方法註解當第二道防線,也讓權限需求在程式碼上一目了然。
資料庫永遠只存 BCrypt 雜湊值,不存明碼:
單向:無法從雜湊值反推原始密碼。
自動加鹽:同一組密碼每次加密結果都不同,彩虹表攻擊無效。
慢速可調:計算成本高,暴力破解代價極大。
登入驗證是把「輸入密碼+雜湊值」交給 BCrypt 比對,而不是解密。
實作 5:註冊與登入 API
先看四個輕量的 DTO:
再看商業邏輯與 Controller:
種子資料與測試帳號
為了讓大家一啟動就能測試,專案在啟動時自動建立測試帳號與範例書籍:
| 帳號 | 密碼 | 角色 |
|---|---|---|
| (可刪除書籍) | ||
完整流程測試(curl 版)
# 1. 登入,取得 token(回應中的 token 欄位)
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "user", "password": "User@123"}'
# 2. 把 token 存進 shell 變數(手動複製貼上也可以)
TOKEN="<把上一步回應中的 token 貼到這裡>"
# 3. 帶著 token 新增書籍 -> 201 Created
curl -i -X POST http://localhost:8080/api/books \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"title": "測試書", "author": "我", "isbn": "9990000000001",
"price": 350, "stock": 5, "description": "curl 測試"}'
# 4. 不帶 token 新增 -> 401 Unauthorized(認證失敗)
curl -i -X POST http://localhost:8080/api/books \
-H "Content-Type: application/json" -d '{}'
# 5. 用一般使用者的 token 刪除 -> 403 Forbidden(授權失敗)
curl -i -X DELETE http://localhost:8080/api/books/1 \
-H "Authorization: Bearer $TOKEN"指令列測試能用,但效率不高——下一章改用 Postman, 讓 token 自動儲存、自動帶入,還能寫自動化驗證腳本。