You are here
Home > !Laravel > ユーザー認証(6)ログインスロットル

ユーザー認証(6)ログインスロットル

スロットルと聞くと、どうしてもバイクのアクセルを想像してしまいます。どちらかというとスピードを出すために。スロットルは正確にはスピードを抑圧する意味で、「スロットル全開」というと、抑制なしで最高のスピードを出すということになります。

ここでのスロットルは、パスワードを意図的に変えて不正にログインしようという試み、たいていはプログラムによる機械的な攻撃、を抑える目的のセキュリティ対策です。具体的には、例えば過去5分間に5回ログインを失敗すると、次回のログインには10分間待たなければならないという仕組みです。もちろんそれですべて防御できるとは言えないですが、少なくとも抑制にはなります。

嬉しいことにこれも、Laravelに機能があります。

スロットル機能はログインで行われるので、以前に説明したログインのプログラムを再度見てみましょう。

まず、AuthControler

namespace App\Http\Controllers\Auth;

use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;

class AuthController extends Controller
{
    use AuthenticatesAndRegistersUsers, ThrottlesLogins;

    ...

ThrottlesLoginsのトレイトがありますね。そして、AuthenticatesAndRegistersUsersのトレイトの定義で使用されている、AuthenticatesUsersのトレイト、以前の紹介ではスロットルの部分を省略したので、今度はそこの部分を含めて見てみましょう。以下のコードのコメントを見てください。

namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;ろぐ
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Lang;

trait AuthenticatesUsers
{
    use RedirectsUsers;

    ...

    public function postLogin(Request $request)
    {
        $this->validate($request, [
            $this->loginUsername() => 'required', 'password' => 'required',
        ]);

        // ThrottlesLoginsは、上のAuthControllerのトレイトして使われているから、$throttleには値を含みます。
        $throttles = $this->isUsingThrottlesLoginsTrait();

    //ここで指定のログインの失敗回数を超えているなら、
       //画面にエラーメッセージを表示して次のログインを指定の時間待たせます。
        if ($throttles && $this->hasTooManyLoginAttempts($request)) {
            return $this->sendLockoutResponse($request);
        }

        //入力の値でログインを試みます。
        $credentials = $this->getCredentials($request);
        if (Auth::attempt($credentials, $request->has('remember'))) {
            return $this->handleUserWasAuthenticated($request, $throttles);
        }

    //ログインが失敗なら、ログインの失敗回数を1増やします。
        if ($throttles) {
            $this->incrementLoginAttempts($request);
        }
        return redirect($this->loginPath())
            ->withInput($request->only($this->loginUsername(), 'remember'))
            ->withErrors([
                $this->loginUsername() => $this->getFailedLoginMessage(),
            ]);
    }
 
    ...
}

というようにログインの失敗回数を追跡しています。

さて、何回まで失敗とか失敗回数を超過したら何分ログインを許さないかはどこで指定されているのでしょう。今度はトレイトのThrottleLoginsを見てみましょう。

namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Cache\RateLimiter;
use Illuminate\Support\Facades\Lang;

trait ThrottlesLogins
{
    //ここです、失敗回数をチェックしているのは。
    protected function hasTooManyLoginAttempts(Request $request)
    {
        return app(RateLimiter::class)->tooManyAttempts(
            $request->input($this->loginUsername()).$request->ip(),
            $this->maxLoginAttempts(), $this->lockoutTime() / 60
        );
    }

    ...

    protected function maxLoginAttempts()
    {
        return property_exists($this, 'maxLoginAttempts') ? $this->maxLoginAttempts : 5;
    }

    protected function lockoutTime()
    {
        return property_exists($this, 'lockoutTime') ? $this->lockoutTime : 60;
    }
}

最初のhasTooManyLoginAttemptsのメソッドでは、maxLoginAttemptslockoutTimeのメソッドがコールされていますね。

それぞれ、ここのクラスのインスタンスで、maxLoginAttemptslockoutTimeの変数が定義されているなら、その値を使いますが、定義なしでは、最高5回、ログインロックは60秒という設定です。

また、失敗回数は、ログイン+IPアドレスの値がキーとなっています。つまり、同じログインで同じIPからログインを試みて連続5回失敗したら、1分のログインロックとなるということです。

最後に、これらの失敗回数の追跡やロックアウトの保持のデータは、どこで管理しているのでしょう。

先のコードで以下の参照をみてください。

use Illuminate\Cache\RateLimiter;

Cacheとして管理しているようです。Laravelでは、Cacheはデフォルトでファイルで管理しています。しかし、ファイルではなくデータベースや高速なメムキャッシュでのデータの管理も可能です。

config/cache.phpにおいて設定するか、.envにおいて、

CACHE_DRIVER=database

と設定することも可能です。データベースと設定するなら、以下のようにmigrationを通して、

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateCacheTable extends Migration
{
    public function up()
    {
                Schema::create('cache', function($table) {
                        $table->string('key')->unique();
                        $table->text('value');
                        $table->integer('expiration');
                });
    }

    public function down()
    {
        //
    }
}

としてcacheテーブルを作成してください。ここで以下のようにデータベースの中身を見ながらテストをすると、実際どのようなレコードが作成されるかわかります。

例えば、ログインに失敗すると、以下のようなレコードが作成されます。

mysql> select * from cache\G;
*************************** 1. row ***************************
       key: laraveltest@gmail.com66.87.77.84
     value: eyJpdiI6IlJQN1Awck9BVzlTWXdnMk4wb0ZOMnc9PSIsInZhb...
expiration: 1449946110
*************************** 2. row ***************************
       key: laraveltest@gmail.com66.87.77.84:lockout
     value: eyJpdiI6Im5pMmg0dm1iREl0TFIwNkkzZTV1V2c9PSIsInZhb...
expiration: 1449946259
2 rows in set (0.00 sec)

最初のレコードは、失敗回数を数えているレコードです。キー(key)として、Eメールアドレス+IPアドレスとなっています。キーの値(value)は暗号化されています。回数が設定を超えると、2番目のレコードが作成されます。キーの最後に:lockoutが追加されていることに注意してください。こちらのレコードの期限は、設定のブロック時間(デフォルトは60秒)を経過した日時となっています。期限が過ぎてアクセスしたときに、これらのレコードは削除されて再度、失敗回数を追跡してきます。

Top