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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 '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方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <?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
1 2 3 4 5 6 ... 'aliases' => [ ... 'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class, 'JWTFactory' => Tymon\JWTAuth\Facades\JWTFactory::class, ],
逻辑实现 注册中间件
这边项目需要,基于官方的中间件重写了一个,如无特殊需要可不注册,直接使用官方的jwt.auth中间件
项目根目录/app/Http/Kernel.php
1 2 3 4 protected $routeMiddleware = [ ... 'api.auth' => \App\Http\Middleware\ApiAuth::class, ];
添加路由 1 2 3 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' );
中间件代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <?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' ); } } }
控制器代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 <?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
1 2 3 4 5 6 7 8 9 10 11 12 13 ... 'ttl' => env('JWT_TTL' , null ),... 'required_claims' => [ 'iss' , 'iat' , 'nbf' , 'sub' , 'jti' , ],
完事,测试一下 给整个postman组添加统一的authorization里的token变量
退出后再退出测试一下中间键
完事,撤