iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0
佛心分享-SideProject30

吃出一個SideProject!系列 第 15

Day15:Auth Service - 登入功能測試與除錯

  • 分享至 

  • xImage
  •  

昨天我們完成了登入功能 JWT 的實作,此時服務應該可以順利啟動。
啟動後我們可以按照第九天的做法,對登入功能進行簡單的測試,看看回應是否都如我們預期。

測試案例

本次一樣列出了正向與負向的測試,負向測試大致上可分成驗證失敗(使用者不存在、密碼錯誤),與驗證前就被檢核擋下來(信箱格式不正確、密碼為空)的情境。

建立 Request

在API Tester的頁面中,到側邊欄此專案下的服務 (auth-service) 下方新增一個 Request,設定內容如下:

  • MethodPOST
  • URLhttp://localhost:8081/users/login

(note:我本來使用 CRUD 來分類 Request,後來覺得還是改以服務名稱進行分類比較合適,已更名為 auth-service,所以跟先前的章節會有些出入。)

案例1:登入成功

這個案例想驗證已透過帳號密碼註冊的使用者是否可成功登入且可取得 JWT,並使用我們在第九天創建的帳號密碼進行測試。

  • Request Body

    { 
    	"email": "test@example.com",
    	"password": "password123"
    }
    
  • 預期結果

    • HTTP Status Code:200 SUCCESSFUL

    • Response Body:

      {
      	"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJzdWIiOiIxIiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImlzcyI6ImF1dGgtc2VydmljZSIsImlhdCI6MTc1OTExNDY3NCwiZXhwIjoxNzU5MTE2NDc0fQ.h4RkjDAMf_FOhGdu2kJzY8FAn1h5LdVt4BvEaFjVxX7FnGCiom2OWMYQBuGF8V0C8IX20kw4TTnbDxIVWwsU2rL0bngcgZRDEZiJUK2cKciA3EOW1dqYdIip8kUw13h8HTBDanB-nNJtTAqI2lGM2XlcpTj7GAn1tbzcX09OvEmivfxfo_5rMCreHhYfSQNcx-iGvdq6GhPfHAI6vsENB_4iuso8WIgA_3uakK8kjsNBwXYfRVr7FiJFtrEsL5V4VSwenMe45v_8PMkj4hp31pe9X9yb3CZX5QckkwRmFaeuZ-SssZx6V3qCONN3HaA6H4e0uAwvVZdzuTwr4LAUkQ",
      	"id": 1,
      	"email": "test@example.com",
      	"roles":[
      		"ROLE_USER"
      	]
      }
      
  • 實際結果:
    實際結果符合預期,回傳了我們在 LoginResponse 定義的內容。
    https://ithelp.ithome.com.tw/upload/images/20250929/20178099YXhVnMQ0GA.png

    我們可以試著將 token 拿到 jwt.io 看看解析出來的 header 與 payload 是否包含正確資訊。結果如下圖,我們將 token 貼上左邊的欄位後,右邊解析內容對應我們昨天在 JwtUtils 設定了 JWT 應該包含哪些 header 與 claim,看起來都正確,初步確認了產製的 JWT 沒有問題。

    https://ithelp.ithome.com.tw/upload/images/20250929/20178099hRI2XAiWTz.png

案例2:登入失敗-密碼錯誤

這個案例想驗證當使用者輸入錯誤的密碼時,登入失敗並回傳 401 Unauthorized。

  • Request Body

    { 
    	"email": "test@example.com",
    	"password": "wrong-password"
    }
    
  • 預期結果

    • HTTP Status Code:401 Unauthorized
  • 實際結果:
    實際結果與預期不符,收到 403 Forbidden,原因於下節分析。
    https://ithelp.ithome.com.tw/upload/images/20250929/20178099ghJW3xP47t.png

案例3:登入失敗-使用者不存在

這個案例想驗證當使用者輸入錯誤的密碼時,登入失敗並回傳 401 Unauthorized。

  • Request Body

    { 
    	"email": "userNotExist@example.com",
    	"password": "password123"
    }
    
  • 預期結果

    • HTTP Status Code:401 Unauthorized
  • 實際結果:
    實際結果與預期不符,收到 403 Forbidden,原因於下節分析。
    https://ithelp.ithome.com.tw/upload/images/20250929/20178099bYuolVT5p8.png

案例4:登入失敗-Email 格式錯誤

這個案例想驗證當使用者輸入錯誤的Email格式時,應該先進行欄位格式檢核,到不了 Controller 就會回應錯誤,所以期待出現的應該是檢核格式錯誤,而非驗證錯誤。

  • Request Body

    { 
    	"email": "not-a-valid-email",
    	"password": "password123"
    }
    
  • 預期結果

    • HTTP Status Code:400 Bad Request

    • Response Body:根據我們在 GlobalException 定義 MethodArgumentNotValidException 的錯誤訊息,預期內容會是:”欄位:錯誤訊息”。
      (我有修改過昨天 Dto 檔案中的 message,所以出現的是我的自訂錯誤訊息,所以有點小落差是正常的)

      {
      	email:"Invalid email format"
      }
      
  • 實際結果:
    符合預期。
    https://ithelp.ithome.com.tw/upload/images/20250929/20178099XQiEi4Nz4M.png

案例 5:登入失敗-密碼為空

這個案例想驗證當使用者沒有輸入密碼時,應該先進行欄位格式檢核,到不了 Controller 就會回應錯誤,所以期待出現的應該是檢核格式錯誤,而非驗證錯誤。

  • Request Body

    { 
    	"email": "test@example.com",
    	"password": ""
    }
    
  • 預期結果

    • HTTP Status Code:400 Bad Request

    • Response Body:根據我們在 GlobalException 定義 IllegalStateException 的錯誤訊息,預期內容會是:”欄位:錯誤訊息”。

      {
      	password:"Password can not be blank"
      }
      
  • 實際結果:
    符合預期。
    https://ithelp.ithome.com.tw/upload/images/20250929/20178099NiBLR9FadD.png

錯誤追蹤:為什麼是 403 而不是 401?

可以看到五個案例中,有兩個不符合我們的預期,也就是案例2與案例3,這兩個案例都是在測試 身分驗證失敗 後的回應是否如我們預期。在這兩個案例中我們期望收到 401 Unauthorized ,讓使用者知道未通過驗證,但不需要知道太多細節。
但我們意外收到了 403 Forbidden 的回應,顯然與之前註冊功能遇到的問題類似,原因應該出在Spring Security的設定。

接下來會依序說明錯誤背後發生了什麼事導致我們收到預期外的回應:

密碼錯誤

  1. DaoAuthenticationProvider 比對密碼後發現與資料庫資料不匹配,內部拋出了一個 BadCredentialsException (由於 Exception 拋出後由 spring 框架處理,這裡目前看不到這個 Exception 出現):
....DaoAuthenticationProvider    : Failed to authenticate since password does not match stored value
  1. 身份驗證失敗後,AnonymousAuthenticationFilter 被觸發,賦予當前請求 匿名用戶 (Anonymous User) 的身份:
...AnonymousAuthenticationFilter  : Set SecurityContextHolder to anonymous SecurityContext
  1. 以匿名身分請求訪問當前 API (/users/login),因為我們在 securityFilterChain 中設定所有請求都須經驗證,因此被阻擋,回傳 403 Forbidden 錯誤。
...HttpSessionRequestCache     : Saved request http://localhost:8081/users/login?continue to session
...Http403ForbiddenEntryPoint     : Pre-authenticated entry point called. Rejecting access
...anyRequest().authenticated() // securityFilterChain 中這個配置導致最後訪問資源權限不足的結果
...

使用者不存在錯誤

使用者不存在的錯誤日誌幾乎和密碼錯誤相同:驗證失敗 -> 設為匿名用戶 -> 未經驗證因此存取被拒 -> 轉發至/error後依然因為未經驗證存取失敗 -> 回傳 403 Forbidden。

如何解決?

我們可以透過設定 AuthEntryPoint 來新增未經認證的請求的入口點,指定其於驗證失敗時,應該返還怎麼樣的錯誤資訊。
明天,我們就來驗證上述對於錯誤的推論是否正確,並介紹 AuthEntryPoint 如何解決我們的問題,讓我們的功能如我們預期的運作!


上一篇
Auth Service - 實作登入功能 (2)
下一篇
Day16:Auth Service - AuthEntryPoint
系列文
吃出一個SideProject!16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言