基于 JWT-Auth 实现 API 验证
如果想要了解其生成Token的算法原理,请自行查阅相关资料
需要提及的几点:
-
使用session存在的问题:
-
session和cookie是为了解决http无状态的方案。session是用户保存在服务器中的状态信息,cookie中则保存jsessionId,请求服务器时,服务器读取jsessionId从而确定用户的身份信息,而session+cookie用在restful接口上破坏了其“无状态”的特性,session运行时都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。这也是restful最致力于通过“无状态”解决的问题。如果使用session,那么restful也就没有什么意义了
-
session降低了系统扩展性。用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力
-
cookie不安全,很容易导致跨站请求伪造攻击(CSRF)
-
-
token存在的问题:
-
如,如何确定token的过期时间?如果token的有效期过短,很容易造成用户用着用着就掉线的情况,降低用户体验。但目前看来好像并没有太好的办法,只能尽量延长token的有效期,或者每隔一次前端自动向服务端请求一次token
-
-
基于 JWT-Auth 的 token 验证体系
(亲测,希望这篇文章让大家少入坑)
-
运行软件版本
-
laravel 5.5
-
-
安装 JWT-Auth 扩展包
composer require tymon/jwt-auth "1.5.*"
-
安装完后在配置文件
config/app.php
中添加注册服务提供者和别名:... 'providers' => [ ... TymonJWTAuthProvidersJWTAuthServiceProvider::class, ] ... 'aliases' => [ ... 'JWTAuth' => TymonJWTAuthFacadesJWTAuth::class, ]
-
发布资源配置
php artisan vendor:publish --provider="TymonJWTAuthProvidersJWTAuthServiceProvider"
-
运行以下命令生成密钥
key
在生成的config/jwt.php
中// 如果运行后报错,提示ERROR:Method TymonJWTAuthCommandsJWTGenerateCommand::handle() does not exist,将vendor ymonjwt-authsrcCommandsJWTGenerateCommand.php文件中的 fire() 方法修改为 handle()即可正常生成秘钥 php artisan jwt:generate
-
编辑
app/Http/Kernel.php
添加jwt.auth
和jwt.refresh
到应用路由中间件数组:protected $routeMiddleware = [ ... 'jwt.auth' => TymonJWTAuthMiddlewareGetUserFromToken::class, // 'jwt.refresh' => TymonJWTAuthMiddlewareRefreshToken::class, ];
-
JWTAuth
自身中间件TymonJWTAuthMiddlewareGetUserFromToken
中包含了对生成token的各类情况的验证,以及异常的抛出。下面是其底层验证类GetUserFromToken::class
:// file_path : vendor ymonjwt-authsrcMiddlewareGetUserFromToken.php <?php /* * This file is part of jwt-auth. * * (c) Sean Tymon <tymon148@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace TymonJWTAuthMiddleware; use TymonJWTAuthExceptionsJWTException; //验证异常类 use TymonJWTAuthExceptionsTokenExpiredException;//token过期异常验证类 class GetUserFromToken extends BaseMiddleware { /** * Handle an incoming request. * * @param IlluminateHttpRequest $request * @param Closure $next * @return mixed */ public function handle($request, Closure $next) { if (! $token = $this->auth->setRequest($request)->getToken()) { return $this->respond('tymon.jwt.absent', 'token_not_provided', 400); } try { $user = $this->auth->authenticate($token); } catch (TokenExpiredException $e) { return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]); } catch (JWTException $e) { return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]); } if (! $user) { return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404); } $this->events->fire('tymon.jwt.valid', $user); return $next($request); } }
其中,调用的
respond
的方法在vendor ymonjwt-authsrcMiddlewareBaseMiddleware.php
文件中/** * Fire event and return the response. * * @param string $event * @param string $error * @param int $status * @param array $payload * @return mixed */ protected function respond($event, $error, $status, $payload = []) { $response = $this->events->fire($event, $payload, true); return $response ?: $this->response->json(['error' => $error], $status); }
可看到,当出现异常需要返回错误信息时,会连带返回一个
fire event
的警告事件对象,这里不做详述。 -
由底层代码中,可以了解到,我们如果想自定义自己所需要的验证方法,可以将这
GetUserFromToken::class
内容复制到我们自己自定义的中间件中。比如:-
创建自定义的验证中间件
AppHttpMiddlewareJwtAuth.php
php artisan make:middleware JwtAuth
-
全部复制到自定义的中间件中后,校验下中间件中需要的类是否应用完全,命名空间是否正确等等,检查无误后根据需要自行定义需要的验证功能。
// demo namespace AppHttpMiddleware; use TymonJWTAuthExceptionsJWTException; use TymonJWTAuthExceptionsTokenExpiredException; use Closure; use TymonJWTAuthMiddlewareBaseMiddleware; class JwtAuth extends BaseMiddleware { public function handle($request, Closure $next) { if (! $token = $this->auth->setRequest($request)->getToken()) { return $this->respond('tymon.jwt.absent', 'token_not_provided', 400); } try { $user = $this->auth->authenticate($token); } catch (TokenExpiredException $e) { return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]); } catch (JWTException $e) { return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]); } if (! $user) { return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404); } $this->events->fire('tymon.jwt.valid', $user); return $next($request); } }
-
定义完成后将自定义的中间件放入
appHttpKernel.php
的中间件数组中。protected $routeMiddleware = [ ... //'jwt.auth' => TymonJWTAuthMiddlewareGetUserFromToken::class, 'jwt.auth_self' => AppHttpMiddlewareJwtAuth::class ];
-
添加好后,即可在
routes/api.php
中对需要控制的api路由进行验证控制Route::group(['middleware'=>'jwt.auth_self'],function(){ // 需要控制的api路由 // ... code });
-
-
我们现在可以对请求来的路由进行token的验证,那么接下来我们就需要生成这个token,让后续访问中间件中的请求路由都携带这个token就能实现验证。这里要提一下在安装
JWT-Auth
过程中生成的配置文件config/jwt.php
<?php return [ ... /* |-------------------------------------------------------------------------- | User Model namespace |-------------------------------------------------------------------------- | | Specify the full namespace to your User model. | e.g. 'AcmeEntitiesUser' | */ // 设置你的用户model,默认为laravel自带的 User model 'user' => 'AppUser', ]
如果需求需要,可在配置文件中修改用户
mode
,但配置的model
中需要引用IlluminateFoundationAuthUser as Authenticatable
,并继承,写法和User model
一致 -
具体的请求登录以及获取token信息,登出等功能实现,可参考此文章
Laravel 5 中使用 JWT(Json Web Token) 实现基于API的用户认证,这里简单提及下常用到的有关其token的方法
<?php namespace AppHttpController; use TymonJWTAuthJWTAuth; class Auth{ public function test (JWTAuth $JWTAuth){ // 获取请求携带中的token $token = $JWTAuth -> getToken(); // 获取token中的用户信息 $user = $JWTAuth -> parseToken() -> authenticate(); // 销毁此次生成的token $JWTAuth->setToken( $JWTAuth->getToken() )->invalidate(); // 自定义生成token,如果需要 $JWTAuth->setToken('foo.bar.baz'); } }