RitoLabo

Laravelの認証機能をログイン失敗回数によってロックしログイン制限を掛ける

  • 公開:
  • 更新:
  • カテゴリ: PHP Laravel
  • タグ: PHP,Laravel,5.5,5.4,5.3,Auth,login,5.6

LaravelなどのPHPフレームワークでWEBアプリケーションを構築する際に、管理画面が必要な場面はよくありますが、「ログインのロックを掛けてほしい」なんて要望も地味に多かったりします。

ログインのロックというのは、
「指定回数ログインに失敗した場合は、一定時間ログイン(認証)自体を行えなくする」
というものです。

これはブルートフォースアタック(総当たり攻撃)のような、片っ端からパスワードを当ててきて認証を破ろうとする攻撃者からデータやサーバを守るために有効な(もちろんこれだけでは完全ではありません)機能の一つでもあります。

もし構築した管理画面にログイン認証ロックが実装されていなかった場合、どうなってしまうでしょうか。

攻撃者は締め出される事なく、何万何億何百億というパターンのパスワードでログインを試みようとするでしょう。そして認証は破られ、情報が盗まれたり改ざんされたり、なりすましの被害に会う場合もあります。

こうした被害を防ぐためにも、ログインのロック機能は大切な機能であったりします。(これだけでは完全に守れない場合もありますが、無いよりは全然ましです)

今回は、LaravelのAuth認証機能拡張第3弾として、ログインのロック機能を実装します。

アジェンダ
  1. 開発環境
  2. ログイン認証制限について
  3. ログイン試行回数とロック時間の設定
  4. ログイン認証ロックの仕組み

開発環境

今回の開発環境は以下の通りです。

  • Linux CentOS 7
  • Apache 2.4
  • MySQL 5.7
  • PHP 7.1/7.2
  • Laravel

Laravelのバージョンに関しては5.6/5.5/5.4/5.3にて動作確認済みです。

また、laravelのソースディレクトリを「laravel/」とします。

基本的な認証機能は導入済みである前提で進めますので、導入までに関しては
Laravelの認証機能でログイン/ユーザ登録/パスワードリセットなどの管理画面を一撃構築する(基本&入門編)
を確認してください。

ログイン認証制限について

実はLaravelでは、Auth認証機能でログイン画面を構築していた場合、既にログインのロック機能が実装されていたりします。

デフォルトでは、ログインに5回失敗すると、同一IPアドレスに対して、60秒のログインロックがかかります。

  • 試行回数:5回
  • ロック時間:60秒
  • ロック対象:IPアドレス

実際にログイン画面からログイン失敗を連発してみてください。5回目に失敗した際にロックがかかり、エラーメッセージも変化します。

通常の認証失敗時のエラーメッセージ
通常の認証失敗時のエラーメッセージ
ログインロックがかかった場合のエラーメッセージ
ログインロックがかかった場合のエラーメッセージ

という事で、Laravelには既に認証回数制限が機能しています!おめでとう!

と言いたいところですが、場合によっては試行回数やロック時間を変更したい事もあると思います。

カスタマイズというわけではありませんが、折角なので、認証制限についての設定と、認証回数制限を行っている部分を紹介していきます。

ログイン試行回数とロック時間の設定

認証回数制限とロックタイムを設定するには、LoginControllerに記述を行います。

laravel/app/Http/Controllers/Auth/LoginController.php
<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/

use AuthenticatesUsers;

/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/account';

/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}

上記はデフォルトの状態です。ここに2つのメンバ変数を追記します。

<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/

use AuthenticatesUsers;

protected $maxAttempts = 2; // ログイン試行回数(回)
protected $decayMinutes = 10; // ログインロックタイム(分)

/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = '/account';

/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}

これだけで完了です。これでログイン試行回数とロックタイムの設定が出来ます。

ちなみに今回は2回のログイン失敗で10分間のログインロックを設定してみました。実際にログインを失敗させて確認してみます。

10分間のログインロックがかかった状態

ロックタイムが60秒に設定されている事が確認できます。

ここで一つ注意すべきは、
「試行回数として設定した数まではログインを試みる事が出来る」
という点です。

なので、このように

protected $maxAttempts = 2;

「2」と設定した場合は、2回までログインを試みる事ができ、3回目はロックが掛かる。という状態になります。

ログイン認証ロックの仕組み

先ほどは、メンバ変数を設定するだけでログインロックをカスタマイズ出来てしまいましたが、これらの仕組みはどこで動いているのでしょうか?

LoginControllerではAuthenticatesUsersトレイトをuseしており、そこでuseされているThrottlesLoginsトレイトに、ログインロック機能が実装されています。

laravel/vendor/laravel/framework/src/Illuminate/Foundation/Auth/ThrottlesLogins.php
<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Cache\RateLimiter;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Support\Facades\Lang;
use Illuminate\Validation\ValidationException;

trait ThrottlesLogins
{
/**
* Determine if the user has too many failed login attempts.
* 失敗したログイン試行回数が上限に達しているかを判定する。
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request)
{
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request), $this->maxAttempts(), $this->decayMinutes()
);
}

/**
* Increment the login attempts for the user.
* ログイン試行回数を増やす
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function incrementLoginAttempts(Request $request)
{
$this->limiter()->hit(
$this->throttleKey($request), $this->decayMinutes()
);
}

/**
* Redirect the user after determining they are locked out.
* ユーザーがロックアウトされたことを確認した後、ユーザーをリダイレクトする
*
* @param \Illuminate\Http\Request $request
* @return void
* @throws \Illuminate\Validation\ValidationException
*/
protected function sendLockoutResponse(Request $request)
{
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);

throw ValidationException::withMessages([
$this->username() => [Lang::get('auth.throttle', ['seconds' => $seconds])],
])->status(423);
}

/**
* Clear the login locks for the given user credentials.
* 指定されたユーザー資格情報のログインロックをクリアする
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function clearLoginAttempts(Request $request)
{
$this->limiter()->clear($this->throttleKey($request));
}

/**
* Fire an event when a lockout occurs.
* ロックアウトが発生したときにイベントを発生させる
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function fireLockoutEvent(Request $request)
{
event(new Lockout($request));
}

/**
* Get the throttle key for the given request.
* 指定されたリクエストのスロットルキーを取得する
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function throttleKey(Request $request)
{
return Str::lower($request->input($this->username())).'|'.$request->ip();
}

/**
* Get the rate limiter instance.
* レートリミッタインスタンスを取得する
*
* @return \Illuminate\Cache\RateLimiter
*/
protected function limiter()
{
return app(RateLimiter::class);
}

/**
* Get the maximum number of attempts to allow.
* 許可する試行の最大回数を取得する
*
* @return int
*/
public function maxAttempts()
{
return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5;
}

/**
* Get the number of minutes to throttle for.
* スロットルする分数を取得する
*
* @return int
*/
public function decayMinutes()
{
return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1;
}
}

これらのThrottlesLoginsトレイトがuseされているので、LoginControllerにてメンバ変数を設定することで、ログイン試行回数とロックタイムを設定することができる。という事になります。

まとめ

Laravelの認証機能には、導入時点で様々な機能が存在していますが、そのどれもがこうしてカスタマイズが可能です。また、こうして既にある機能をカスタマイズすることによって、ゼロから作るよりも格段に早く、そして堅牢なシステムを構築することが出来ます。

基本実装されているログインロックですが、カスタマイズも簡単に可能なので、是非試してみてください。