Laravel 5.5 使用 Jwt-Auth 实现 API 用户认证

2021-06-16 08:03

阅读:415

先来说明一下我想要达成的效果,我希望用户提供账号密码前来登录。如果登录成功,那么我会给前端颁发一个 access _token ,设置在 header 中以请求需要用户认证的路由。

同时我希望如果用户的令牌如果过期了,可以暂时通过此次请求,并在此次请求中刷新该用户的 access _token,最后在响应头中将新的 access _token 返回给前端,这样子可以无痛的刷新 access _token ,用户可以获得一个很良好的体验,所以开始动手写代码。

执行如下命令以新建一个中间件:

php artisan make:middleware RefreshToken

中间件代码如下:

RefreshToken.php

php

namespace App\Http\Middleware;

use 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 RefreshToken extends BaseMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Closure $next
     *
     * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException
     *
     * @return mixed
     */
    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);
    }
}

更新异常处理的 Handler

由于我们构建的是 api 服务,所以我们需要更新一下 app/Exceptions/Handler.php 中的 render方法,自定义处理一些异常。

Handler.php

php

namespace App\Exceptions;

use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

class Handler extends ExceptionHandler
{
   ...

   /**
    * Render an exception into an HTTP response.
    *
    * @param  \Illuminate\Http\Request $request
    * @param  \Exception $exception
    * @return \Illuminate\Http\Response
    */
   public function render($request, Exception $exception)
   {
       // 参数验证错误的异常,我们需要返回 400 的 http code 和一句错误信息
       if ($exception instanceof ValidationException) {
           return response([‘error‘ => array_first(array_collapse($exception->errors()))], 400);
       }
       // 用户认证的异常,我们需要返回 401 的 http code 和错误信息
       if ($exception instanceof UnauthorizedHttpException) {
           return response($exception->getMessage(), 401);
       }

       return parent::render($request, $exception);
   }
}

测试

// 使用 Auth 登录用户,如果登录成功,则返回 201 的 code 和 token,如果登录失败则返回
return ($token = Auth::guard(‘api‘)->attempt($request->only(‘email‘,‘password‘)))
    ? response([‘token‘ => ‘bearer ‘ . JWTAuth::fromUser(Auth::guard(‘api‘)->user())], 201)
    : response([‘error‘ => ‘账号或密码错误‘], 400);
    }
注意:在认证成功后其他经过RefreshToken中间件的api路由在请求的时候需要在header上加上Authorization字段,其值为登录返回的token


评论


亲,登录后才可以留言!