PHP

Laravel 試行系の回数バリデーションを実装してみた

Laravel

 

何回も試行すると一定時間たたないと実行できないというやつです。

 

ログインを例に概念的に書いているのでそのままでは動かない…と思う💦

ふわっと参考までに🐱✨

 

limit_login_attemptsテーブル

id user_id login_attempt attempt_time allow_time
1 1 2 2020-06-22 17:29:38 null
2 2 0 2020-06-22 18:29:38 2020-06-22 19:29:38

 

  • 試行するたびにlogin_attemptの回数をカウント
  • login_attemptが3回以上の時にロックアウト
    ・ログインでallow_timeに現在日時から1時間を加えた日時を設定
    ・allow_timeを迎えるまでログインさせない。
    ・login_attemptを0にリセット
  • ログイン試行した時にattempt_timeが現在日時より1時間以上過去の場合
    ・login_attemptをリセットする
    ・通常の試行を行う
    login_attemptを1に繰り上げる。attempt_timeの更新

 

マイグレーションファイル

<?php

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

class CreateLimitLoginAttempts extends Migration
{
    const TABLE = 'limit_login_attempts';

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        if (Schema::hasTable(self::TABLE)) {
            return;
        }

        Schema::create(self::TABLE, function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->bigInteger('user_id')->unsigned();
            $table->Integer('login_attempt')->unsigned()->default(0)->comment('試行回数');
            $table->dateTime('attempt_time')->nullable()->comment('最終ログイン失敗日時');
            $table->dateTime('allow_time')->nullable()->comment('ロック解除日時');
            $table->timestamps();
            $table->foreign('user_id')
                    ->references('id')
                    ->on('users')
                    ->onDelete('cascade');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists(self::TABLE);
    }
}

 

 

 

インターフェイス

<?php

namespace App\Repositories\Login;

use App\Repositories\Traits\ResourceConstructInterface;

interface LimitLoginAttemptInterface extends ResourceConstructInterface
{
    public function store(int $user_id);
    public function firstOrCreateUserId(int $user_id);
    public function lock(int $user_id);
}

 

 

リポジトリ

<?php

namespace App\Repositories\Login;

use App\Repositories\AbstractRepository;
use App\Entities\LimitLoginAttempt;
use Carbon\Carbon;

class LimitLoginAttemptRepository extends AbstractRepository implements LimitLoginAttemptInterface
{
    const RESET_COUNT = 0;
    const ADD_HOUR_FOR_LOCK = 1;

    public function __construct(LoginAttempt $resource)
    {
        $this->resource = $resource;
    }

    /**
     * user_idを指定してインスタンスを返却
     *
     * @param int $user_id
     * @return object(App\Entities\LoginAttempt)
     */
    public function firstOrCreateUserId(int $user_id): LoginAttempt
    {
        return ($this->resource->firstOrCreate(['user_id' => $user_id]));
    }

    /**
     * 試行回数を繰り上げて保存
     *
     * @param int $user_id
     */
    public function store(int $user_id): void
    {
        $now = Carbon::now();
        $login_attempt = $this->resource->where('user_id', '=', $user_id)->first();
        $attempt_time = $login_attempt->attempt_time;
        $attempt_time = new Carbon($attempt_time);
        $attempt_time->addHour(self::ADD_HOUR_FOR_LOCK);

        // 十分に最後の設定変更から時間が空いている場合は試行回数をリセット
        if ($attempt_time < $now) {
            $login_attempt->login_attempt = self::RESET_COUNT;
        }
        $login_attempt->login_attempt++;
        $login_attempt->attempt_time = Carbon::now();
        $login_attempt->save();
    }

    /**
     * 試行回数を超えた時のロック処理
     *
     * @param int $user_id
     */
    public function lock(int $user_id): void
    {
        $now_lock = Carbon::now();
        $login_attempt = $this->resource->where('user_id', '=', $user_id)->first();
        $login_attempt->login_attempt = self::RESET_COUNT;
        $login_attempt->attempt_time = Carbon::now();
        $login_attempt->allow_time = $now_lock->addHour(self::ADD_HOUR_FOR_LOCK);// 制限解除する日時設定
        $login_attempt->save();
    }
}

 

 

app/Http/Providers/RepositoryServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class RepositoryServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {

・・・

        $this->app->bind(
            \App\Repositories\Login\LimitLoginAttemptInterface::class,
            \App\Repositories\Login\LimitLoginAttemptRepository::class
        );
    }
}

 

 

サービス

<?php

namespace App\Services;

use App\Repositories\User\UserInterface;
use App\Repositories\Login\LoginAttemptInterface;

use Carbon\Carbon;


class LoginService extends AbstractClass
{
    const LIMIT_LOGIN_ATTEMPT = 3;
    const LIMIT_LOGIN_ATTEMPT_INIT = 0;

    public function __construct(
        UserInterface $r_c,
        LimitLoginAttemptInterface $r_la
    ) {
        parent::__construct();
        $this->r_c = $r_c;
        $this->r_la = $r_la;
    }

    /**
     * 実行
     *
     * @param int $user_id
     * @return void
     */
    public function execute(int $user_id)
    {
        if (!is_numeric($user_id)) {
            $this->error('指定された user_id は無効です。整数を入力してください。user_id:' . $user_id);
            exit;
        }

        // ログイン
        if ((isset($user_id))) {
            $this->userLogin($user_id);
        }
        abort(422, '無効な値が投げられました');
    }

    /**
     * ログイン試行回数バリデーションを通してログイン
     *
     * @param int $user_id
     */
    public function userLogin($user_id)
    {
        try {
            // 試行回数チェック
            $login_attempt = $this->r_la->firstOrCreateUserId($user_id);
            $now_date = Carbon::now();
            if ($login_attempt->change_attempt >= self::LIMIT_LOGIN_ATTEMPT) {
                $this->r_la->lock($user_id);
                $this->error('試行制限がかかりました。1時間後に改めて実行してください');
                exit;
            }

            //ログイン実行する
            \Exec::Login();
            // ドメイン変更試行回数を加算
            $this->r_la->store($user_id);
        } catch (\Exception $e) {
            \Log::error($e->getMessage());
            $this->error('Failed: ' . $e->getMessage());
        }
    }
}

 

 

 

 

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)