Laravelでログインに3回失敗したらリダイレクトする

Laravel Laravel

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\AuthThrottlesLoginsに実装があります。

開いてみるとこんな感じです。
ここでは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\CacheRateLimiterクラスに実装があります。

渡されたキーでキャッシュに保存している失敗回数を取得して、$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トレイトの方にログイン時の処理が色々書いています。

オーバーライドすることで、処理がカスタマイズできるように色々定義されています。
ログイン周りの処理をカスタマイズした場合にはこちらを一度確認してみてください。

コメント

タイトルとURLをコピーしました