跳至主要内容

Authentication Controller - 統一登入端點

概述

AuthController 已重構為統一的登入端點,/auth/login 現在支援兩種登入方式:

  1. Bearer Token 登入: 如果請求中包含 Authorization: Bearer <token> header,優先使用 token 驗證並更新過期時間
  2. 傳統用戶名密碼登入: 如果沒有 Bearer token,則使用 request body 中的用戶名和密碼進行驗證

此外還保留了專門的 /auth/refresh 端點用於 token 刷新。

API 端點

1. 統一登入端點

端點: POST /auth/login

說明: 智能登入端點,根據請求內容自動選擇認證方式

Bearer Token 登入方式

請求標頭:

Authorization: Bearer <your-jwt-token>

請求體: 無需提供(可選)

成功響應 (200 OK):

{
"accessToken": "new.jwt.token.here",
"tokenType": "Bearer"
}

用戶名密碼登入方式

請求標頭: 無需 Authorization header

請求體:

{
"username": "your-username",
"password": "your-password"
}

成功響應 (200 OK):

{
"accessToken": "new.jwt.token.here",
"tokenType": "Bearer"
}

錯誤響應

缺少憑證 (400 Bad Request):

{
"error": "Missing login credentials",
"timestamp": 1698765432100
}

無效憑證 (401 Unauthorized):

{
"error": "Invalid username or password",
"timestamp": 1698765432100
}

無效 Token (401 Unauthorized):

{
"error": "Invalid or expired token",
"timestamp": 1698765432100
}

2. Token 刷新端點

端點: POST /auth/refresh

說明: 專門用於刷新現有 token 的端點

請求標頭:

Authorization: Bearer <your-jwt-token>

成功響應 (200 OK):

{
"accessToken": "refreshed.jwt.token.here",
"tokenType": "Bearer"
}

錯誤響應 (401 Unauthorized):

{
"error": "Invalid or expired token",
"timestamp": 1698765432100
}

使用範例

JavaScript/Fetch API

智能登入 - Bearer Token 優先

const smartLogin = async (token, credentials) => {
const headers = {
'Content-Type': 'application/json'
};

let body = null;

// 如果有 token,優先使用 Bearer token 登入
if (token) {
headers['Authorization'] = `Bearer ${token}`;
} else if (credentials) {
// 否則使用用戶名密碼登入
body = JSON.stringify(credentials);
} else {
throw new Error('Either token or credentials must be provided');
}

const response = await fetch('/auth/login', {
method: 'POST',
headers: headers,
body: body
});

if (response.ok) {
const data = await response.json();
console.log('Login successful, new token:', data.accessToken);
return data.accessToken;
} else {
const error = await response.json();
console.error('Login failed:', error.error);
throw new Error(error.error);
}
};

// 使用範例
// 1. 使用現有 token 登入(更新過期時間)
smartLogin('existing.jwt.token').then(newToken => {
console.log('Token refreshed via login:', newToken);
});

// 2. 使用用戶名密碼登入
smartLogin(null, { username: 'user', password: 'pass' }).then(token => {
console.log('Traditional login successful:', token);
});

專門的 Token 刷新

const refreshToken = async (token) => {
const response = await fetch('/auth/refresh', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});

if (response.ok) {
const data = await response.json();
console.log('Refreshed token:', data.accessToken);
return data.accessToken;
} else {
const error = await response.json();
console.error('Refresh failed:', error.error);
throw new Error(error.error);
}
};

cURL 範例

Bearer Token 登入

# 使用現有 token 登入(優先級最高)
curl -X POST http://localhost:8080/auth/login \
-H "Authorization: Bearer your.existing.token" \
-H "Content-Type: application/json"

用戶名密碼登入

# 傳統用戶名密碼登入
curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"your-username","password":"your-password"}'

Token 刷新

# 專門的 token 刷新
curl -X POST http://localhost:8080/auth/refresh \
-H "Authorization: Bearer your.jwt.token.here" \
-H "Content-Type: application/json"

重構改進

優勢

  1. 統一端點: 客戶端只需要調用一個登入端點,簡化了 API 使用
  2. 智能選擇: 自動根據請求內容選擇最適合的認證方式
  3. 向後兼容: 現有的用戶名密碼登入方式完全保持不變
  4. 靈活性: 支援 token 續期和傳統登入兩種場景

決策邏輯

  1. 首先檢查是否有 Authorization: Bearer <token> header
  2. 如果有 Bearer token,進行 token 驗證並生成新 token
  3. 如果沒有 Bearer token,檢查 request body 是否包含用戶名和密碼
  4. 如果兩者都沒有,返回錯誤訊息

安全性考慮

  • Bearer token 優先級高於用戶名密碼
  • 所有 token 都經過嚴格驗證
  • 適當的錯誤處理和日誌記錄
  • 統一的錯誤響應格式

測試

專案包含了完整的單元測試,涵蓋:

  • Bearer token 登入成功場景
  • 用戶名密碼登入成功場景
  • 缺少憑證的錯誤場景
  • 無效 token 的錯誤場景
  • Token 刷新功能測試