2a0e9255ae
- Account lockout:連續失敗 5 次鎖定 15 分鐘(Fixed Window,Cache)
- Member / Provider 各自獨立 namespace
- Email 正規化(strtolower+trim)
- 不存在帳號不累計計數(防 Cache 污染)
- companion key 記錄 locked_until(ISO 8601)
- OAuth CSRF 防護:移除 stateless(),手動 state 生成與驗證
- session('oauth_state') + session('state') 雙寫確保 Socialite 內建驗證通過
- state mismatch redirect 至 /login?error=oauth_failed
- throttle 從 5,1 調整為 10,1,避免 IP throttle 在 lockout 前觸發
- NormalizesEmail Trait、config/auth_lockout.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.6 KiB
3.6 KiB
1. 帳號鎖定設定檔與共用 Helper
- 1.1 [後端] 建立
config/auth_lockout.php,定義max_attempts(envLOCKOUT_MAX_ATTEMPTS, 預設 5)與decay_minutes(envLOCKOUT_DECAY_MINUTES, 預設 15) - 1.2 [後端] 建立
app/Traits/NormalizesEmail.phpTrait,定義normalizeEmail(string $email): string(strtolower(trim($email))),在AuthController與ProviderAuthController均 use 此 Trait
2. Member 帳號鎖定邏輯
- 2.1 [後端] 修改
app/Http/Controllers/API/AuthController.php→loginMember():對 request email 執行strtolower(trim())正規化後,先查 Cache keylogin_failures:member:{email},若已達閾值讀取login_expires_at:member:{email}回傳 HTTP 423(含locked_until) - 2.2 [後端] DB 查無帳號時直接回傳 HTTP 401(訊息與密碼錯誤相同),不遞增計數
- 2.3 [後端] 登入失敗(帳號存在但密碼錯)時:若
login_failureskey 不存在則用Cache::put()設初始值 1 並同步寫入login_expires_atcompanion key(TTL 相同);若 key 已存在則只Cache::increment()(不重設 TTL);回傳 423 時讀取login_expires_atkey,若缺失則 fallback 為now()->addMinutes($decay_minutes)並寫Log::warning - 2.4 [後端] 登入成功時
Cache::forget('login_failures:member:{email}')與Cache::forget('login_expires_at:member:{email}')
3. Provider 帳號鎖定邏輯
- 3.1 [後端] 修改
app/Http/Controllers/API/ProviderAuthController.php→loginProvider():同 Member 邏輯(email 正規化、不存在帳號不遞增、companion key、fixed window),Cache key 前綴改為login_failures:provider:/login_expires_at:provider: - 3.2 [後端] 登入成功時清除兩個 Cache key(failures + expires_at)
4. OAuth State 驗證
- 4.1 [後端] 修改
app/Http/Controllers/API/SocialAuthController.php→redirectToProvider():移除stateless(),產生bin2hex(random_bytes(32))存session('oauth_state'),附加state於 redirect URL - 4.2 [後端] 修改
handleProviderCallback():移除stateless(),讀取request->state與session('oauth_state')比對;不符或缺失時回傳 HTTP 400;比對後立即session()->forget('oauth_state')
5. 前端錯誤處理
- 5.1 [前端] 修改
frontend/src/views/LoginView.vue:axios catch 中判斷error.response?.status === 423,顯示response.data.message(帳號鎖定提示),而非泛用錯誤 - 5.2 [前端] 修改
frontend/src/views/CoachLoginView.vue:同上,處理 423 狀態碼 - 5.3 [前端] 修改
frontend/src/views/AuthCallbackView.vue:當後端 OAuth callback 回傳 400 時,redirect 至/login?error=oauth_failed - 5.4 [前端] 修改
frontend/src/views/LoginView.vue:若route.query.error === 'oauth_failed',顯示「OAuth 授權失敗,請重新嘗試」提示
6. 整合驗證
- 6.1 [整合測試] 手動測試 Member 登入:連續失敗 5 次後收到 423,等待 15 分鐘後或改正確密碼(需清除 Cache)後可登入
- 6.2 [整合測試] 手動測試 Provider 登入:同上
- 6.3 [整合測試] 手動測試 OAuth:正常流程可完成;直接訪問 callback URL(無 state)收到 400 並 redirect 至
/login?error=oauth_failed - 6.4 [整合測試] 確認
storage/framework/sessions目錄在 Docker 容器中有寫入權限(session 存 state 不報錯) - 6.5 [部署] 確認生產環境
.env設定CACHE_DRIVER=redis;驗證方式:php artisan tinker→Cache::put('test', 1, 10); Cache::get('test');回傳 1 即 Redis 正常