Laravelでログインに失敗して、ロックアウト判定した際に処理をしたいという要件があったので、認証処理をカスタマイズしてみました。
この記事ではLaravelで「3回ログインに失敗したらgoogleにリダイレクトする」という処理のコード載せて解説しています。
ロックアウトにする回数・分数とロックアウト時の処理は簡単に変更することが可能です。
この記事で確認してみてください。
今回確認したLaravelのバージョンは6になります。
ロックアウト時に処理をする
サンプルコードと解説を載せています。
サンプルコード
Laravelで認証処理を追加するとapp/Http/Controllers/Auth/LoginController.php
がログイン時に使用されるコントローラーになります。
コメントを省いたコードは下記のような感じです。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $maxAttempts = 2;
protected $redirectTo = RouteServiceProvider::HOME;
public function __construct()
{
$this->middleware('guest')->except('logout');
}
protected function sendLockoutResponse(Request $request)
{
$this->clearLoginAttempts($request);
return redirect("https://google.com");
}
}
追加したコードは$maxAttempts
フィールドとsendLockoutResponse
メソッドです。
解説
$maxAttempts
はロックアウトにする回数です。
2回に設定しています。
このように設定しておくと、ログインに2回失敗してもロックアウトされませんが、3回目でロックアウトされます。
sendLockoutResponse
メソッドはロックアウト時に処理されるメソッドです。
ここに処理を書くと、ロックアウト時に処理することができます。
ここで処理しているのは
まず$this->clearLoginAttempts($request);
とやっています。
このメソッドはロックアウトされた状態をクリアしています。
その後にGoogleにリダイレクトしてみました。
これだけで3回間違えるとリダイレクトするという処理ができます。
今回の実装は1分間に3回間違えるとロックアウトされます。
1分間の箇所を変えるには$decayMinutes
フィールドを$maxAttempts
と同じように定義して、数値を入れておきます。
protected $decayMinutes = 3;
と定義すると3分になって、「3分の間に3回間違えたら…」という感じになります。
ログインから追って、少し詳しい解説
ログイン処理から確認してみました…
ログイン時に動く処理は、上記のコードで書いているAutheticatesUsers
トレイトに書いています。
その中の処理が下記にようになっています。
public function login(Request $request)
{
$this->validateLogin($request);
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
回数の判定をしているのは、hasTooManyLoginAttempts
メソッドを呼んでいる箇所です。
このメソッドの中で$maxAttempts
に設定した値を使って判定しています。
失敗した回数はLaravelのCache機能で保持しています。
失敗した回数が$maxAttempts
より大きくなったらtrueが返ってきます。
trueが返ってきたら、if
の中を通りますね。
$this->fireLockoutEvent($request)
はロックアウトイベントを発生させます。
その後に、リターンで$this->sendLockoutResponse($request);
としています。
このsendLockoutResponse
メソッドの処理をオーバーライドしてリダイレクトするようにしているといった感じです。
オーバーライドする元の処理は、AuthenticatesUsers
の中でuse
されている下記のファイルで定義されています。
vendor/laravel/framework/src/Illuminate/Foundation/Auth/ThrottlesLogins.php
オーバーライドする元のメソッドは下記のようになっています。
protected function sendLockoutResponse(Request $request)
{
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);
throw ValidationException::withMessages([
$this->username() => [Lang::get('auth.throttle', [
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
])],
])->status(Response::HTTP_TOO_MANY_REQUESTS);
}
やっている内容は秒数を取得して、バリデーションエラーとしてメッセージをフロントに返しています。
返されるメッセージはデフォルトでは下記です。
Too many login attempts. Please try again in :seconds seconds.
これはresources/lang/en/auth.php
に定義されています。
:secondsの箇所に「~秒後にもう一度試してください」というメッセージの秒数が当てはまります。
Laravelのロックアウト判定の箇所について
回数判定の箇所が気になったので、hasTooManyLoginAttempts
をもう少し詳しく見てみました。
Illuminate\Foundation\Auth
のThrottlesLogins
に実装があります。
開いてみるとこんな感じです。
ここではtooManyAttempts
に引数を渡して呼び出しているだけですね。
protected function hasTooManyLoginAttempts(Request $request)
{
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request), $this->maxAttempts()
);
}
第1引数の$this->throttleKey($request)
は「Str::lower($request->input($this->username())).'|'.$request->ip()
」が返ってきます。
例としては”email@example.com|192.168.0.1″みたいな文字列が返ってきます。(throttleKeyメソッドの中で返しています)
これは、画面から入力したemail(usernameメソッドで設定)とipの連結文字列です。
第2引数の$this->maxAttempts()
は$maxAttempts
で設定したロックアウト回数です。
渡した先のtooManyAttempts
を見てみるとこんな感じです。
Illuminate\Cache
のRateLimiter
クラスに実装があります。
渡されたキーでキャッシュに保存している失敗回数を取得して、$maxAttempts
と比べています。
public function tooManyAttempts($key, $maxAttempts)
{
if ($this->attempts($key) >= $maxAttempts) {
if ($this->cache->has($key.':timer')) {
return true;
}
$this->resetAttempts($key);
}
return false;
}
attempts
はこんな感じで、Laravelのキャッシュからキーを使って、失敗した回数を取得しています。
public function attempts($key)
{
return $this->cache->get($key, 0);
}
つまり、キャッシュで失敗した回数を保存して、上限を超えていたらロックアウトするといった処理ですね。
気にしないといけないのは、キーが変わると別のデータとしてカウントされるので、画面から入力されたusername(email)が変わったらカウントされないという感じになっています。
emailが変わらずにパスワードを上限以上失敗した時にエラーとなりますね。
終わりに
今回はログインでロックアウトした場合の処理をカスタマイズしてみました。
Auth/LoginController.php
にはあまり処理が書いていませんが、AuthenticatesUsers
トレイトの方にログイン時の処理が色々書いています。
オーバーライドすることで、処理がカスタマイズできるように色々定義されています。
ログイン周りの処理をカスタマイズした場合にはこちらを一度確認してみてください。
コメント