Authentication Controller - 統一登入端點
概述
AuthController 已重構為統一的登入端點,/auth/login 現在支援兩種登入方式:
- Bearer Token 登入: 如果請求中包含
Authorization: Bearer <token>header,優先使用 token 驗證並更新過期時間 - 傳統用戶名密碼登入: 如果沒有 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"
重構改進
優勢
- 統一端點: 客戶端只需要調用一個登入端點,簡化了 API 使用
- 智能選擇: 自動根據請求內容選擇最適合的認證方式
- 向後兼容: 現有的用戶名密碼登入方式完全保持不變
- 靈活性: 支援 token 續期和傳統登入兩種場景
決策邏輯
- 首先檢查是否有
Authorization: Bearer <token>header - 如果有 Bearer token,進行 token 驗證並生成新 token
- 如果沒有 Bearer token,檢查 request body 是否包含用戶名和密碼
- 如果兩者都沒有,返回錯誤訊息
安全性考慮
- Bearer token 優先級高於用戶名密碼
- 所有 token 都經過嚴格驗證
- 適當的錯誤處理和日誌記錄
- 統一的錯誤響應格式
測試
專案包含了完整的單元測試,涵蓋:
- Bearer token 登入成功場景
- 用戶名密碼登入成功場景
- 缺少憑證的錯誤場景
- 無效 token 的錯誤場景
- Token 刷新功能測試