security(auth): P2 帳號鎖定 + OAuth state 驗證
- 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>
This commit is contained in:
@@ -18,11 +18,19 @@ class SocialAuthController extends Controller
|
||||
*/
|
||||
public function redirectToGoogle()
|
||||
{
|
||||
return Socialite::driver('google')
|
||||
$state = bin2hex(random_bytes(32));
|
||||
session()->put('oauth_state', $state);
|
||||
|
||||
$redirect = Socialite::driver('google')
|
||||
->scopes(['openid', 'profile', 'email'])
|
||||
->with(['access_type' => 'offline', 'prompt' => 'consent']) // 這裡要求 prompt=consent 才能每次都獲取 refresh token
|
||||
->stateless()
|
||||
->with(['access_type' => 'offline', 'prompt' => 'consent', 'state' => $state])
|
||||
->redirect();
|
||||
|
||||
// Socialite 在 redirect() 內自己寫了一個 random state 進 session('state')。
|
||||
// 用我們的 state 覆蓋,確保 user() 的內建驗證與 URL 中的 state 一致。
|
||||
session()->put('state', $state);
|
||||
|
||||
return $redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,9 +38,16 @@ class SocialAuthController extends Controller
|
||||
*/
|
||||
public function handleGoogleCallback(Request $request)
|
||||
{
|
||||
$requestState = $request->input('state');
|
||||
$sessionState = session()->pull('oauth_state');
|
||||
|
||||
if (!$requestState || !$sessionState || !hash_equals($sessionState, $requestState)) {
|
||||
return redirect(config('app.frontend_url') . '/login?error=oauth_failed');
|
||||
}
|
||||
|
||||
try {
|
||||
// 獲取 Google 用戶資訊
|
||||
$googleUser = Socialite::driver('google')->stateless()->user();
|
||||
// 獲取 Google 用戶資訊(Socialite 內建 state 驗證此時也會通過)
|
||||
$googleUser = Socialite::driver('google')->user();
|
||||
|
||||
// 查找相關的社交帳號
|
||||
$socialAccount = SocialAccount::where('provider', 'google')
|
||||
|
||||
Reference in New Issue
Block a user