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 刷新方案 感谢大家能看到这里,本次教程到此结束。希望猪哥的教程能给大家带来一点启发,最后祝大家工作顺利万事如意!




