Laravel 6はjwt(tymon/jwt-auth)によりAPIユーザの無感リフレッシュTOKENを実現

82780 ワード

Laravel 6はjwtによってAPIユーザーの無感リフレッシュTOKENを実現する
  • 1、TOKENって何ですか
  • 2、jwtとは何ですか
  • 3、jwtインストール&構成
  • 3.1、composerによる
  • のインストール
  • 3.2、配布構成
  • 3.3、暗号鍵
  • を生成する.
  • 3.4、authを修正する.php
  • 3.5、Userモデル
  • を変更
  • 4、カスタム認証ミドルウェア&&異常
  • 4.1、インタフェースは
  • に統一的に戻る
  • 4.2、ミドルウェアの作成
  • 4.2、異常処理
  • 4.2.1、異常集合
  • を作成する
  • 4.2.2、異常処理
  • 5、コントローラ&&ルーティング
  • 5.1、作成コントローラ
  • 5.2、ルーティング
  • 5.3、フロントエンドTOKEN更新
  • 1、TOKENって何?
    TOKENはトークンに翻訳され、身分を識別する証明書であり、身分証明書に似ている.TOKENは本質的に大きな文字列であり、最もよく使われるシーンはインタフェースドッキングの認証である.TOKENは1回のログイン検証により認証文字列を得,その後他の要求が毎回この認証文字列を携帯すると認証を完了する.
    2、jwtって何?
    jwt全称json web token(データネットワークトークンに翻訳)jwtは、データをネットワーク上で安全に伝送するための正規化されたtokenである.jwt使用シーン:1.ステータスレスRESTful API 2.SSOワンポイントログイン
    3、jwtインストール&構成
    3.1、composerによるインストール
    composer require tymon/jwt-auth
    

    3.2、配布構成
    php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
    

    プロファイルが自動的に生成されます:config/jwt.php
    3.3、暗号鍵の生成
    jwt-authはすでにArtisanコマンドを定義しており、Secretを生成するのに便利です.shellで次のコマンドを実行するだけでいいです.
    php artisan jwt:secret
    

    .envファイルの下で暗号化キーを生成
    3.4、authを修正する.php
    'guards' => [
            'web' => [
                'driver' => 'session',
                'provider' => 'users',
            ],
    
            'api' => [
                'driver' => 'jwt', //     token   jwt
                'provider' => 'users'
            ],
        ],
    

    3.5、Userモデルの変更
  • ここでは後で開発しやすいようにappuser.phpをappModelsuserに移動php、グローバル検索AppUserをAppModelsUser
  • に変更
  • userモデルにJWTSubjectインタフェースを実装させ、getJWTIdentifiier()およびgetJWTCustomClaims()メソッド
  • を実装する.
    
    
    namespace App\Models;
    
    use Tymon\JWTAuth\Contracts\JWTSubject;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    use Illuminate\Notifications\Notifiable;
    
    class User extends Authenticatable implements JWTSubject
    {
    
    	use Notifiable;
        /**
         * The attributes that are mass assignable.
         *
         * @var array
         */
        protected $fillable = [
            'name', 'password',
        ];
    
        /**
         * The attributes that should be hidden for arrays.
         *
         * @var array
         */
        protected $hidden = [
            'password',
        ];
    
        public function getJWTIdentifier()
        {
            return $this->getKey();
        }
    
        public function getJWTCustomClaims()
        {
            return [];
        }
    }
    

    4、カスタム認証ミドルウェア&&異常
    4.1、インタフェースが統一的に戻る
    新規一括戻りtrait ApiResponse
    
    
    namespace App\Http\Helpers\Api;
    
    use Symfony\Component\HttpFoundation\Response as FoundationResponse;
    
    trait ApiResponse
    {
        /**
         * @var int
         */
        protected $statusCode = FoundationResponse::HTTP_OK;
        protected $token = '';
    
        /**
         * @return mixed
         */
        public function getStatusCode()
        {
            return $this->statusCode;
        }
    
        /**
         * @param $statusCode
         * @return $this
         */
        public function setStatusCode($statusCode,$httpCode=null)
        {
            $httpCode = $httpCode ?? $statusCode;
            $this->statusCode = $statusCode;
            return $this;
        }
    
        /**
         * @param $statusCode
         * @param null $httpCode
         * @return $this
         */
        public function setToken($token)
        {
            $this->token = $token;
            return $this;
        }
    
        /**
         * @param $data
         * @param array $header
         * @return mixed
         */
        public function respond($data)
        {
            $response = response()->json($data, $this->getStatusCode());
            if($this->token) {
                $response->headers->set('Authorization', 'Bearer '.$this->token);
            }
            return $response;
        }
    
        /**
         * @param $status
         * @param array $data
         * @param null $code
         * @param array $header
         * @return mixed
         */
        public function status($status, array $data, $code = null)
        {
            if ($code){
                $this->setStatusCode($code);
            }
            $status = [
                'status' => $status,
                'code' => $this->statusCode
            ];
    
            $data = array_merge($status,$data);
            return $this->respond($data);
        }
    
        /**
         * @param $message
         * @param int $code
         * @param string $status
         * @return mixed
         */
        /*
         *   
         * data:
         *  code:422
         *  message:xxx
         *  status:'error'
         */
        public function failed($message, $code = FoundationResponse::HTTP_BAD_REQUEST,$status = 'error')
        {
            return $this->setStatusCode($code)->message($message,$status);
        }
    
        /**
         * @param $message
         * @param string $status
         * @return mixed
         */
        public function message($message, $status = "success")
        {
            if(!is_array($message)) {
                $message = [$message];
            }
            return $this->status($status,[
                'message' => $message
            ]);
        }
    
        /**
         * @param string $message
         * @return mixed
         */
        public function internalError($message = "Internal Error!")
        {
            return $this->failed($message,FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
        }
    
        /**
         * @param string $message
         * @return mixed
         */
        public function created($message = "created")
        {
            return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
                ->message($message);
        }
    
        /**
         * @param $data
         * @param string $status
         * @param array $header
         * @return mixed
         */
        public function success($data, $status = "success")
        {
            return $this->status($status,compact('data'));
        }
    
        /**
         * @param string $message
         * @return mixed
         */
        public function notFond($message = 'Not Fond!')
        {
            return $this->failed($message,Foundationresponse::HTTP_NOT_FOUND);
        }
    }
    
    

    4.2、ミドルウェアの作成
    ユーザーはアカウントのパスワードを提供してログインします.ログインに成功した場合、インタフェースはフロントエンドのTOKENに戻り、ユーザーのTOKENが期限切れになった場合、今回のリクエストを一時的に通過し、今回のリクエストでそのユーザーのTOKENをリフレッシュし、最後に応答ヘッドで新しいtokenをフロントエンドに戻すことで、痛みのないTOKENをリフレッシュすることができ、ユーザーは良い体験を得ることができます.ミドルウェアの生成:
    php artisan make:middleware Api/RefreshTokenMiddleware
    

    コードは次のとおりです.
    
    
    namespace App\Http\Middleware\Api;
    
    use Illuminate\Support\Facades\Auth;
    use Closure;
    use Tymon\JWTAuth\Exceptions\JWTException;
    use Tymon\JWTAuth\Http\Middleware\BaseMiddleware;
    use Tymon\JWTAuth\Exceptions\TokenExpiredException;
    use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
    
    //   ,        jwt   BaseMiddleware
    class RefreshTokenMiddleware extends BaseMiddleware
    {
        /**
         * @param $request
         * @param Closure $next
         * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed
         * @throws JWTException
         */
        public function handle($request, Closure $next)
        {
            //             token,         。
            $this->checkForToken($request);
    //            try   ,    token        TokenExpiredException    
            try {
                //          ,       
                if ($this->auth->parseToken()->authenticate()) {
                    return $next($request);
                }
                throw new UnauthorizedHttpException('jwt-auth', '   ');
            } catch (TokenExpiredException $exception) {
                //        token        TokenExpiredException   ,                 token           
                try {
                    //       token
                    $token = $this->auth->refresh();
                    //                  
                    Auth::guard('api')->onceUsingId($this->auth->manager()->getPayloadFactory()->buildClaimsCollection()->toPlainArray()['sub']);
                } catch (JWTException $exception) {
                    //         ,    refresh     ,        ,      。
                    throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage());
                }
            }
    
            //           token
            return $this->setAuthenticationHeader($next($request), $token);
        }
    }
    
    

    4.2、異常処理
    4.2.1、異常集合の作成
    異常集合ファイルを作成するphp
    
    
    namespace App\Http\Helpers\Api;
    
    use Exception;
    use Illuminate\Auth\Access\AuthorizationException;
    use Illuminate\Auth\AuthenticationException;
    use Illuminate\Database\Eloquent\ModelNotFoundException;
    use Illuminate\Database\QueryException;
    use Illuminate\Http\Request;
    use Illuminate\Validation\ValidationException;
    use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
    use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
    use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
    use Tymon\JWTAuth\Exceptions\TokenInvalidException;
    
    class ExceptionReport
    {
        use ApiResponse;
    
        /**
         * @var Exception
         */
        public $exception;
        /**
         * @var Request
         */
        public $request;
    
        /**
         * @var
         */
        protected $report;
    
        /**
         * ExceptionReport constructor.
         * @param Request $request
         * @param Exception $exception
         */
        function __construct(Request $request, Exception $exception)
        {
            $this->request = $request;
            $this->exception = $exception;
        }
    
        /**
         * @var array
         */
        //        ,              HTTP   
        //           
        public $doReport = [
            AuthenticationException::class => ['   ', 401],
            ModelNotFoundException::class => ['      ', 404],
            AuthorizationException::class => ['     ', 403],
            ValidationException::class => [],
            UnauthorizedHttpException::class => ['          ', 422],
            TokenInvalidException::class => ['token   ', 400],
            NotFoundHttpException::class => ['       ', 404],
            MethodNotAllowedHttpException::class => ['       ', 405],
            QueryException::class => ['    ', 401],
        ];
    
        public function register($className, callable $callback)
        {
    
            $this->doReport[$className] = $callback;
        }
    
        /**
         * @return bool
         */
        public function shouldReturn()
        {
            //       json  ajax      
    //        if (! ($this->request->wantsJson() || $this->request->ajax())){
    //
    //            return false;
    //        }
            foreach (array_keys($this->doReport) as $report) {
                if ($this->exception instanceof $report) {
                    $this->report = $report;
                    return true;
                }
            }
    
            return false;
    
        }
    
        /**
         * @param Exception $e
         * @return static
         */
        public static function make(Exception $e)
        {
    
            return new static(\request(), $e);
        }
    
        /**
         * @return mixed
         */
        public function report()
        {
            if ($this->exception instanceof ValidationException) {
    //            $error = array_key_first($this->exception->errors());
                return $this->failed(current($this->exception->errors()), $this->exception->status);
            }
            $message = $this->doReport[$this->report];
            return $this->failed($message[0], $message[1]);
        }
    
        public function prodReport()
        {
            return $this->failed('     ', '500');
        }
    }
    
    

    4.2.2、異常処理
    app/Exceptions/Handler.phpのrenderメソッドでは、いくつかの例外をカスタマイズします.
    
    
    namespace App\Exceptions;
    
    use App\Http\Helpers\Api\ExceptionReport;
    use Exception;
    use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
    
    class Handler extends ExceptionHandler
    {
        /**
         * A list of the exception types that are not reported.
         *
         * @var array
         */
        protected $dontReport = [
            //
        ];
    
        /**
         * A list of the inputs that are never flashed for validation exceptions.
         *
         * @var array
         */
        protected $dontFlash = [
            'password',
            'password_confirmation',
        ];
    
        /**
         * Report or log an exception.
         *
         * @param  \Exception  $exception
         * @return void
         *
         * @throws \Exception
         */
        public function report(Exception $exception)
        {
            parent::report($exception);
        }
    
        /**
         * Render an exception into an HTTP response.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Exception  $exception
         * @return \Symfony\Component\HttpFoundation\Response
         *
         * @throws \Exception
         */
        public function render($request, Exception $exception)
        {
            if($request->is("api/*")){
                //          ExceptionReport
                $reporter = ExceptionReport::make($exception);
                if ($reporter->shouldReturn()){
                    return $reporter->report();
                }
                if(env('APP_DEBUG')){
                    //    ,         
                    return parent::render($request, $exception);
                }else{
                    //    ,    ,   500
                    return $reporter->prodReport();
                }
            }
            return parent::render($request, $exception);
        }
    }
    
    

    5、コントローラ&&ルーティング
    5.1、コントローラの作成
  • インタフェースコントローラApi/Clontrollerを作成する.php
  • php artisan make:controller Api/Controller
    

    次に、次の方法を追加します.
    
    
    namespace App\Http\Controllers\Api;
    
    use App\Http\Helpers\Api\ApiResponse;
    use App\Http\Controllers\Controller as BaseController;
    
    class Controller extends BaseController
    {
    
        use ApiResponse;
        //      Api    
    }
    
    
  • インタフェースコントローラApi/UserControllerを作成する.php
  • php artisan make:controller Api/UserController
    

    次に、次の方法を追加します.
    
    
    namespace App\Http\Controllers\Api;
    
    use App\Models\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    
    class UserController extends Controller
    {
        /**
         * @param Request $request
         * @return mixed
         */
        public function login(Request $request){
            $token = Auth::guard('api')->attempt(['name'=>$request->name,'password'=>$request->password]);
            if($token) {
                return $this->setStatusCode(201)->success(['token' => 'bearer ' . $token]);
            }
            return $this->failed('       ',400);
        }
    
        /**
         * @return mixed
         */
        public function loginOut(){
            Auth::guard('api')->logout();
            return $this->success('    ...');
        }
    
    
        /**
         *           
         * @param Request $request
         * @return mixed
         */
        public function info(Request $request){
    
            $user = $request->user();
            return $this->success($user);
        }
    
        /**
         * @param Request $request
         * @return mixed
         * @throws \Exception
         */
        public function register(Request $request)
        {
            $member = [
                'name' => $request->name,
                'password' => $request->password,
            ];
            User::create($member);
            return $this->setStatusCode(201)->success('      ');
        }
    
        /**
         * @param Request $request
         * @return mixed
         */
        public function changePw(Request $request)
        {
            $user = $request->user();
            $user->update(['password' => $request->password]);
            $token = Auth::guard('api')->refresh();
            return $this->setStatusCode(201)->setToken($token)->success('      ');
        }
    
    }
    
    

    5.2、ルート
    routes\api.phpには、次のコードが追加されました.
    Route::prefix('user/')->group(function() {
            //    
            Route::post('register', 'UserrController@register')->name('user.register');
            //    
            Route::post('login', 'UserrController@login')->name('user.login');
    
            Route::middleware('api.refresh')->group(function() {
                //      
                Route::get('info', 'UserrController@info')->name('user.info');
                Route::post('changepw', 'UserrController@changePw')->name('user.changepw');
                //    
                Route::post('logout', 'UserrController@logout')->name('users.logout');
            });
        });
    

    5.3、フロントエンドTOKEN更新
    フロントエンドは、返されたヘッダにTOKENが含まれているかどうかを監視し、ある場合はローカルTOKENを置き換え、APIユーザがTOKENを無感にリフレッシュすることを実現する.