Laravel jwt 实现多用户认证并实现刷新token全过程
大家好,最近工作繁忙,很久没有更新文章了,今天给大家写一篇关于laravel 结合 jwt 实现多用户认证 并实现 刷新token的的全过程!希望能帮助到大家下面开始我们的教程
首先我们要去安装 laravel jwt的 包
安装
1:
composer require tymon/jwt-auth
官方地址:https://jwt-auth.readthedocs.io/en/develop/laravel-installation
有什么不懂的可以查看官方文档
由于官方文档写的比较简单,下面我们开始详细的安装过程
安装完成后 开始我们的配置
2:
添加服务提供者(大于 Laravel 5.5 版本的跳过)
/app/config/app.php 下修改
'providers' => [ .... Tymon\JWTAuth\Providers\LaravelServiceProvider::class, ]
'aliases' => [ ... 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class, ]
为了防止意外发生建议 高版本的也配置上避免出错。
3: 生成配置文件
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
4: 生成 secret
php artisan jwt:secret
安装初步完成! 下面开始配置多用户 下面我已 我们创建一张 rider表 测试下
我们先生成我们需要的模型
php artisan make:model Rider -m
然后我们在 database 文件夹下找到 我们刚才创建的迁移文件
创建以下字段
Schema::create('riders', function (Blueprint $table) { $table->increments('id'); $table->string('name',20)->comment('姓名'); $table->string('tel',20)->unique()->comment('手机号'); $table->string('password'); $table->timestamps(); });
这里为了测试方便就只创建了姓名 手机号 密码 我们使用手机号和密码登录
然后我们执行 laravel 6.0 以下执行
php artisan make:auth
laravel 6.0移除了make:auth 需要单独安装 composer require laravel/ui 来单独安装这个权限包
5:修改模型为jwt 认证的模型
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Tymon\JWTAuth\Contracts\JWTSubject; class Rider extends Authenticatable implements JWTSubject { /** * Get the identifier that will be stored in the subject claim of the JWT. * * @return mixed */ public function getJWTIdentifier() { return $this->getKey(); } /** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims() { return []; } /** * 配置Jwt-Auth验证表格的主键(默认ID) */ public function getAuthIdentifierName() { return 'id'; } protected $fillable = [ 'name', 'password', 'tel', ]; }
5: 修改配置文件 app/config/auth.php 配置 Authentication Guards
'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\User::class, ], 'riders' => [ 'driver' => 'eloquent', 'model' => App\Rider::class, ], ],
6配置 User Providers
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], 'rider' => [ 'driver' => 'jwt', 'provider' => 'riders', ], ],
现在我们的多用户配置完成,为了能在jwt中正常使用 我们还需要配置一个中间件 让laravel内置的权限系统能区分我们自己定义的 guards 让jwt 认识它
我们执行 以下命令
php artisan make:middleware AssignGuard
我们打开 app/http/Middleware 文件夹找到 AssignGuard 这个文件
写入如下代码
<?php namespace App\Http\Middleware; use Closure; class AssignGuard { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next, $guard = null) { // 这里的作用是指定当前请求是使用哪种 guard,比如 auth()->shouldUse('rider') if($guard != null){ auth()->shouldUse($guard); } return $next($request); } }
现在我们的jwt已经可以使用内置的auth权限 识别用户了 下面我们创建一个控制器测试下
执行 以下命令
php artisan make:controller rider/AuthController
找到我们生成的控制器 app/Http/Controllers/AuthController
代码如下:
<?php namespace App\Http\Controllers\rider; use App\Rider; use Illuminate\Http\Request; use App\Http\Controllers\Controller; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; class AuthController extends Controller { public function __construct() { $this->middleware(['assign.guard:rider'], ['except' => ['login','register']]); } /** * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response * @throws \Illuminate\Validation\ValidationException */ public function login(Request $request) { // 验证规则,由于业务需求,这里我更改了一下登录的用户名,使用手机号码登录 $rules = [ 'tel' => [ 'required', 'exists:riders', ], 'password' => 'required|string|min:6|max:20', ]; $params = $this->validate($request, $rules); // 使用 Auth 登录用户,如果登录成功,则返回 201 的 code 和 token,如果登录失败则返回 return ($token = Auth::guard('rider')->attempt($params)) ? response()->json(['token' => 'Bearer ' . $token],200) : response()->json(['error' => '账号或密码错误'],422); } /** * Get the authenticated User. * * @return \Illuminate\Http\JsonResponse */ public function me() { return response()->json(Auth::guard('rider')->user()); } public function register(Request $request) { $tel = $request->tel; $password = $request->password; $check_password = $request->check_password; if (!$tel || !$password) { return response()->json(['success' => false, 'message' => '用户名、密码必填!']); } if ($check_password != $password) { return response()->json(['success' => false, 'message' => '两次密码输入不一致!']); } $admin = Rider::where('tel', $tel)->first(); if ($admin) { return response()->json(['success' => false, 'message' => '用户名已被注册!']); } $password = Hash::make($password); $admin = Rider::create([ 'tel' => $tel, 'password' => $password ]); return response()->json(['success' => true, 'message' => '注册成功!', 'admin' => $admin]); } /** * Log the user out (Invalidate the token). * * @return \Illuminate\Http\JsonResponse */ public function logout() { auth('rider')->logout(); return response()->json(['message' => 'Successfully logged out']); } /** * Refresh a token. * * @return \Illuminate\Http\JsonResponse */ public function refresh() { return $this->respondWithToken(auth('rider')->refresh()); } /** * Get the token array structure. * * @param string $token * * @return \Illuminate\Http\JsonResponse */ protected function respondWithToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth('rider')->factory()->getTTL() * 60 ]); } }
上面定义了登录 注册 和刷新 token 的方法 由于我们要实现 token在过期之前自动刷新token 这里的刷新token的方法 也就没什么用了,暂且保留
代码解释:
在上面的控制器方法中 我们 在构造方法里面 使用了 我们自己创建的中间件
assign.guard:rider
这个中间件后面是告诉laravel 权限系统我们要使用rider守卫来认证我们的jwt 用户系统,不太了解的可以看看我的其他文章有专门解释中间件传递参数的
下面我们往表中插入测试数据测试下
测试效果如下:
已经可以成功请求到了
但是我们发现当jwt 有token 过期时间默认是60分钟 如果token过期了我们就没办法再次刷新了 如下图
有没有办法可以让他 快过期之前重新刷新个新的token 呢 答案是有的,我们要做的就是 在它快过期10分钟之前 重新刷新一次token 就行了
我们需要注册一个中间件 来拦截所有的请求给客户端下发token
执行 以下命令
php artisan make:middleware RefreshToken
写入如下代码:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Support\Facades\Auth; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenExpiredException; use Tymon\JWTAuth\Http\Middleware\BaseMiddleware; // 注意,我们要继承的是 jwt 的 BaseMiddleware class RefreshToken extends BaseMiddleware { /** * @param $request * @param Closure $next * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\Response|mixed * @throws JWTException */ public function handle($request, Closure $next) { //BaseMiddleware内方法 try{ if ($userInfo = $this->auth->parseToken()->authenticate()) { $time = Auth::guard('rider')->payload()['exp']; //刷新Token if(($time - time()) < 10*60 && ($time - time()) > 0){ $token = Auth::guard('rider')->refresh(); if($token){ $request->headers->set('Authorization', 'Bearer '.$token); }else{ return response(['code'=>401,'msg'=>'The token has been blacklisted'],401); } // 在响应头中返回新的 token $respone = $next($request); if(isset($token) && $token){ $respone->headers->set('Authorization', 'Bearer '.$token); } return $respone; } return $next($request); } throw new UnauthorizedHttpException('jwt-auth', '未登录'); }catch (TokenExpiredException $exception){ //是否可以刷新,刷新后加入到响应头 throw new UnauthorizedHttpException('jwt-auth', $exception->getMessage()); } } }
需要注意的是 之前我们测试时候没有加上这个中间件 我们需要在 AuthController 的构造方法里面添加上这个中间件
public function __construct() { $this->middleware(['assign.guard:rider','refresh'], ['except' => ['login','register']]); }
注意中间件的顺序 assign.guard:rider 这个中间件一定要在前面不然jwt无法区分是那个guard
解释下上面的代码 我们先尝试拿到用户信息 如果拿不到就会抛出异常消息 拦截 然后我们在 异常消息处理类返回401错误,如果能拿到用户信息 就
在token过期的前10分钟刷新token 然后返回新的token给 header头 前端进行无痛刷新即可!
具体设置如下:在app/Exceptions/Handeler.php 中我们捕获异常消息 处理jwt 的异常 如果不处理 前端将会返回401的网页 这不是我们预期的结果
代码如下:
<?php namespace App\Exceptions; use Exception; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Validation\ValidationException; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Tymon\JWTAuth\Exceptions\JWTException; 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 */ public function report(Exception $exception) { parent::report($exception); } /** * 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()->json([ 'msg' => 'token过期', 'status' => 401 ],401); } if ($exception instanceof JWTException) { return response()->json([ 'msg' => 'token验证失败请重新登录', 'status' => 401 ],401); } return parent::render($request, $exception); } }
这样我们就能正常返回jwt的报错信息给前端了,下面我们开始验证下 自动刷新的结果是否符合我们的预期
这里为了测试方便我把token 的过期时间设置为6分钟方便我们测试 在 app/config/jwt.php 文件设置过期时间
'ttl' => env('JWT_TTL', 6),
我们来看下测试结果 这里我们可以看到
从图上看出我们已经成功刷新了token 这样前端 只需拦截下heaer头判断是否有token 如果有就替换掉旧的token 这样可以无感知的刷新token 结果符合预期!这是本人一直在使用的比较成熟的
jwt token 刷新方案 感谢大家能看到这里,本次教程到此结束。希望猪哥的教程能给大家带来一点启发,最后祝大家工作顺利万事如意!