Laravel8+Redis コマンドでワンタイムトークンの為の

8 min read読了の目安(約8000字

前提

  • ワンタイムトークンの前段であるトークンの操作処理を作り方を掲載
  • php8
  • redisの操作はphpredis
  • OSはubuntu20
  • タイトルはトークンとしてあるが、以降はシグネチャー(signature) と表現する
  • 掲載するコードは説明不要の最小限の内容且つ実用性のあるもの

特にワンタイムトークンとして利用しなければいけない訳ではなく、削除処理をロジックに組み込まなければ一定期間有効なトークンとして利用可能

作るコマンド

コマンド 効用
admin:signature:issue 発行
admin:signature:validate 有効性の検証
admin:signature:remove 無効化

トークンを操作する処理の作成

bash
mkdir -p app/Repositories/Admin
touch app/Repositories/Admin/SignatureRepositoryInterface.php
touch app/Repositories/Admin/SignatureRepository.php
mkdir -p app/Services/Admin
touch app/Services/Admin/AccountServiceInterface.php
touch app/Services/Admin/AccountService.php

シグネチャの保存・取得・削除のみを提供する処理

app/Repositories/Admin/SignatureRepositoryInterface.php
<?php
declare(strict_types=1);
namespace App\Repositories\Admin;

use Illuminate\Support\Facades\Redis;

interface SignatureRepositoryInterface{
    public function save(string $signature, int $lifetimeSeconds): bool;
    public function get(string $signature): ?string;
    public function delete(string $signature): void;   
}
app/Repositories/Admin/SignatureRepository.php
<?php
declare(strict_types=1);
namespace App\Repositories\Admin;

use Illuminate\Support\Facades\Redis;
use Carbon\Carbon;
use App\Repositories\Admin\SignatureRepositoryInterface;

class SignatureRepository implements SignatureRepositoryInterface{
    
    public function save(string $signature, $lifetimeSeconds): bool{
        return Redis::set($signature
                        , Carbon::now()->addSeconds($lifetimeSeconds)->timestamp
                        , 'ex'
                        , $lifetimeSeconds
                        , 'nx');
    }
    
    public function get(string $signature): ?string{
        return Redis::get($signature);
    }
    
    public function delete(string $signature): void{
        Redis::command('del', [$signature]);
    }
}

SignatureRepositoryをサービスコンテナにバインド

artisan
php artisan make:provider RepositoryServiceProvider
app/Providers/RepositoryServiceProvider.php
use App\Repositories\Admin\SignatureRepositoryInterface;
use App\Repositories\Admin\SignatureRepository;

    public function register()
    {
        $this->app->bind(SignatureRepositoryInterface::class, SignatureRepository::class);
    }

Signatureの発行・検証・無効化を提供する処理

app/Services/Admin/AccountServiceInterface.php
<?php
declare(strict_types=1);
namespace App\Services\Admin;

use Illuminate\Support\Facades\Redis;

interface AccountServiceInterface{

    public function issueSignature(int $expired): ?string;
    public function validateSignature(string $signature): bool;
    public function revokeSignature(string $signature): void;

}
app/Services/Admin/AccountService.php
<?php
declare(strict_types=1);
namespace App\Services\Admin;

use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;
use App\Services\Admin\AccountServiceInterface;
use App\Repositories\Admin\SignatureRepositoryInterface;

class AccountService implements AccountServiceInterface{

    private SignatureRepositoryInterface $signatureRepository;

    public function __construct(SignatureRepositoryInterface $signatureRepository){
        $this->signatureRepository = $signatureRepository;
    }

    public function issueSignature(int $expired): ?string{
        $signature = Str::random(64);
        return match($this->signatureRepository->save($signature, $expired)){
            true => $signature
            ,false => null
        };
    }
    public function validateSignature(string $signature): bool{
        return match($this->signatureRepository->get($signature)){
            null => false
            ,default => true            
        };
    }
    public function revokeSignature(string $signature): void{
        $this->signatureRepository->delete($signature);
    }
}

コマンドの作成

発行コマンド

artisan
php artisan make:command Admin/IssueSignatureCommand
app/Console/Commands/Admin/IssueSignatureCommand.php
<?php
namespace App\Console\Commands\Admin;

use Illuminate\Console\Command;
use App\Services\Admin\AccountService;

class IssueSignatureCommand extends Command{
    protected $signature = 'admin:signature:issue 
                            {expired=1800 : 有効期限(秒)指定}';
			    
    protected $description = 'ワンタイムキーを発行する';
    
    public function __construct(){
        parent::__construct();
    }
    
    public function handle(AccountService $accountService){
        $expired = $this->argument('expired');
        $result = $accountService->issueSignature($expired);
        match($result){
            null => $this->error('Issue failure. Please retry issue signature')
            ,default => (function($parent, $result)  {
                            $parent->info('Signature issued!');
                            $parent->line($result);
                        })($this, $result)
        };
    }
}

発行コマンドの動作確認

  • ヘルプの表示
artisan
php artisan admin:signature:issue -h
Description:
  ワンタイムキーを発行する

Usage:
  admin:signature:issue [<expired>]

Arguments:
  expired               有効期限(秒)指定 [default: "1800"]
  • 発行
artisan
php artisan admin:signature:issue 3000
Signature issued!
G2ZmtPVG0jNtACnwcGm36cf9egVEH4FYkUJ3sAPzULTWkPgrRZYU1SUB31LtXFi7

検証コマンド

artisan
php artisan make:command Admin/ValidateSignatureCommand
app/Console/Commands/Admin/ValidateSignatureCommand.php
<?php

namespace App\Console\Commands\Admin;

use Illuminate\Console\Command;
use App\Services\Admin\AccountService;

class ValidateSignatureCommand extends Command{
    protected $signature = 'admin:signature:validate
                            {signature : 有効性の確認をするワンタイムキー}';
			    
    protected $description = 'ワンタイムキーの有効性を確認する';
    
    public function __construct(){
        parent::__construct();
    }
    
    public function handle(AccountService $accountService){
        match($accountService->validateSignature($this->argument('signature'))){
            true => $this->info('Signature is valid')
            ,default => $this->error('Signature is invalid')
        };
    }
}

検証コマンドの動作確認

  • ヘルプ表示
artisan
php artisan admin:signature:validate -h
Description:
  ワンタイムキーの有効性を確認する

Usage:
  admin:signature:validate <signature>

Arguments:
  signature             有効性の確認をするワンタイムキー
  • 有効状態の検証
artisan
php artisan admin:signature:validate G2ZmtPVG0jNtACnwcGm36cf9egVEH4FYkUJ3sAPzULTWkPgrRZYU1SUB31LtXFi7
Signature is valid

無効化コマンド

artisan
php artisan make:command Admin/RevokeSignatureCommand
app/Console/Commands/Admin/RevokeSignatureCommand.php
<?php
namespace App\Console\Commands\Admin;

use Illuminate\Console\Command;
use App\Services\Admin\AccountService;

class RevokeSignatureCommand extends Command{
    protected $signature = 'admin:signature:revoke
                            {signature : 無効化するワンタイムキー}';
			    
    protected $description = 'ワンタイムキーを無効化する';

    public function __construct(){
        parent::__construct();
    }

    public function handle(AccountService $accountService){
        $this->line('Revoke signature: '.$this->argument('signature'));
        $accountService->revokeSignature($this->argument('signature'));
        $this->info('Signature revoked.');
    }
}

-ヘルプの表示

artisan
php artisan admin:signature:revoke -h
Description:
  ワンタイムキーを無効化する

Usage:
  admin:signature:revoke <signature>

Arguments:
  signature             無効化するワンタイムキー
  • 無効化
artisan
php artisan admin:signature:revoke G2ZmtPVG0jNtACnwcGm36cf9egVEH4FYkUJ3sAPzULTWkPgrRZYU1SUB31LtXFi7
Revoke signature: G2ZmtPVG0jNtACnwcGm36cf9egVEH4FYkUJ3sAPzULTWkPgrRZYU1SUB31LtXFi7
Signature revoked.

無効状態のの検証

artisan
php artisan admin:signature:validate G2ZmtPVG0jNtACnwcGm36cf9egVEH4FYkUJ3sAPzULTWkPgrRZYU1SUB31LtXFi7
Signature is invalid

ワンタイムトークンたらしめる為にはシグネチャを一度利用したタイミングシグネチャを無効化する必要があります