emmmmm,发现一个问题;如果jwt过期时间设为2小时,则为绝对意义上的2小时,而不是用户无操作之后的2小时,这是为了保证token的安全,防止token被窃取后,一直使用,永远不过期。所以要用 JWTAuth::parseToken()->refresh()
刷新token,返回给前端; 当然也可以设置无过期时间
jwt简单了解 jwt全称json web token,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519)。特别适用于分布式站点的单点登录(SSO)场景。
jwt认证流程
客户端表单提交用户认证信息发送到服务端
服务端验证用户信息,生成jwt(包括header,payload,signature),jwt的组成后面再具体研究
服务端返回token给客户端
客户端存储token,并在后续每次请求服务端时在Http请求的header中的Authorization中上送token,(也可不放在header中,但建议在header中上送,保证安全)
服务端收到请求,验证jwt,然后处理业务逻辑返回结果
jwt认证与session认证的优势
用户认证后会存储用户认证记录在session中,而session通常保存在内存中,这就导致随用户增多,内存使用也会逐步增加。jwt则不存在内存问题。
session认证的方式,认证记录存储在服务器的内存中,这就导致分布式应用中,其他服务器上无法获取认证记录。
在laravel中具体使用 安装composer包 composer require tymon/jwt-auth
配置 生成配置文件 config/jwt.php php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
生成加密秘钥(会在env中添加一个JWT_SECRET配置项) php artisan jwt:secret
更换laravel的auth配置 编辑 项目根目录/config/auth.php
'defaults' => [ 'guard' => 'jwt' , 'passwords' => 'users' , ], ... 'guards' => [ 'web' => [ 'driver' => 'session' , 'provider' => 'users' , ], 'api' => [ 'driver' => 'token' , 'provider' => 'users' , 'hash' => false , ], 'jwt' => [ 'driver' => 'jwt' , 'provider' => 'users' , 'hash' => false , ], ], ... 'providers' => [ 'users' => [ 'driver' => 'eloquent' , 'model' => App\Models\Admin ::class , ], ],
修改用户模型
继承JWTSubject类并实现getJWTIdentifier及getJWTCustomClaims方法
<?php namespace App \Models ;use Illuminate \Database \Eloquent \Factories \HasFactory ;use Illuminate \Database \Eloquent \SoftDeletes ;use Illuminate \Foundation \Auth \User as Authenticatable ;use Illuminate \Notifications \Notifiable ;use Spatie \Permission \Traits \HasRoles ;use Tymon \JWTAuth \Contracts \JWTSubject ;class Admin extends Authenticatable implements JWTSubject { use HasFactory , Notifiable ,HasRoles ,SoftDeletes ; protected $table = 'admins' ; protected $fillable = [ 'name' , 'phone' , 'email' , 'password' , 'is_super' ]; protected $hidden = [ 'password' , 'remember_token' , ]; protected $casts = [ 'email_verified_at' => 'datetime' , ]; public function getJWTIdentifier ( ) { return $this ->getKey (); } public function getJWTCustomClaims ( ): array { return []; } }
注册facade
不注册也行,直接是用auth()函数也是可以的
编辑 项目根目录/config/app.php
... 'aliases' => [ ... 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth ::class , 'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory ::class , ],
逻辑实现 注册中间件
这边项目需要,基于官方的中间件重写了一个,如无特殊需要可不注册,直接使用官方的jwt.auth中间件
项目根目录/app/Http/Kernel.php
protected $routeMiddleware = [ ... 'api.auth' => \App\Http\Middleware\ApiAuth ::class , ];
添加路由 Route ::namespace ('Auth' )->post ('register' , 'AuthController@register' )->name ('erp.register' );Route ::namespace ('Auth' )->post ('login' , 'AuthController@login' )->name ('erp.login' );Route ::namespace ('Auth' )->middleware ('api.auth' )->post ('logout' , 'AuthController@logout' )->name ('erp.logout' );
中间件代码 <?php namespace App \Http \Middleware ;use App \Exceptions \Erp \PermissionForbiddenException ;use App \Exceptions \Erp \RouteNotFoundException ;use App \Exceptions \Erp \UnauthorizedException ;use Symfony \Component \HttpKernel \Exception \UnauthorizedHttpException ;use Tymon \JWTAuth \Exceptions \TokenBlacklistedException ;use Tymon \JWTAuth \Exceptions \TokenExpiredException ;use Tymon \JWTAuth \Exceptions \TokenInvalidException ;use Tymon \JWTAuth \Http \Middleware \BaseMiddleware ;class ApiAuth extends BaseMiddleware //继承jwt 的BaseMiddleware ,以使用他的方法 { public function handle ($request , \Closure $next ) { try { $this ->checkForToken ($request ); if ($this ->auth->parseToken ()->authenticate ()) { } throw new UnauthorizedException ('未登录' ); } catch (UnauthorizedHttpException $exception ) { throw new UnauthorizedException ('未获取到token' ); } catch (TokenExpiredException | TokenBlacklistedException $exception ) { throw new UnauthorizedException ('token令牌已失效,请重新登录' ); } catch (TokenInvalidException $exception ) { throw new UnauthorizedException ('无效的token' ); } } }
控制器代码 <?php namespace App \Http \Controllers \Api \Auth ;use App \Exceptions \Erp \InvalidArgumentException ;use App \Exceptions \Erp \RepeatException ;use App \Exceptions \Erp \ServiceException ;use App \Http \Controllers \Controller ;use App \Models \Admin ;use Illuminate \Foundation \Validation \ValidatesRequests ;use Illuminate \Http \Request ;use Tymon \JWTAuth \Exceptions \JWTException ;use Tymon \JWTAuth \Facades \JWTAuth ;use Validator ;class AuthController extends Controller { use ValidatesRequests ; public function register (Request $request ): array { $data = $request ->all (); $validator = Validator ::make ($data , [ 'phone' => 'required' , 'name' => 'required|max:64' , 'password' => 'required|max:64' , ], [ 'phone.required' => '手机号必填' , 'phone.digits' => '手机号格式错误' , 'name.required' => '用户名必填' , 'name.max' => '用户名长度超限' , 'password.required' => '密码必填' , 'password.max' => '密码长度超限' , ]); if ($validator ->fails ()) { throw new InvalidArgumentException ($validator ->errors ()->messages ()); } try { $admin = Admin ::query ()->create ([ 'phone' => $data ['phone' ], 'name' => $data ['name' ], 'password' => bcrypt ($data ['password' ]) ]); }catch (\Exception $exception ){ throw new RepeatException ('手机号已注册,请直接登录' ); } $token = JWTAuth ::fromUser ($admin ); return [ 'token' => $token ]; } public function login (Request $request ): array { $data = $request ->all (); $validator = Validator ::make ($data , [ 'name' => 'required' , 'password' => 'required|max:64' , ], [ 'name.required' => '用户名必填' , 'password.required' => '密码必填' , 'password.max' => '密码长度超限' , ]); if ($validator ->fails ()) { throw new InvalidArgumentException ($validator ->errors ()->messages ()); } $credentials = $request ->only ('name' , 'password' ); try { if (! $token = JWTAuth ::attempt ($credentials )) { throw new InvalidArgumentException ('用户名或者密码错误' ); } } catch (JWTException $e ) { throw new ServiceException ('token 无法生成' ); } return [ 'token' => $token ]; } public function logout ( ) { JWTAuth ::setToken (JWTAuth ::getToken ())->invalidate (); return []; } }
编辑 项目根目录/config/jwt.php
... 'ttl' => env ('JWT_TTL' , null ),... 'required_claims' => [ 'iss' , 'iat' , 'nbf' , 'sub' , 'jti' , ],
完事,测试一下 给整个postman组添加统一的authorization里的token变量
退出后再退出测试一下中间键
完事,撤