一、声明
1. 本文来自
https://packagist.org/packages/phper666/jwt-auth
2. 迁移原因
hyperf 目前在网上没什么接口认证教程,正好本人在 composer 组件库中看到该组件,认为做的比较不错,所以搬迁到 learnku 的 hyperf 社区供大家学习参考!
二、JWT-auth 的特点
jwt-auth 支持单点登录、多点登录、支持注销 token(token 会失效)、支持刷新 token
1. 单点登录
只会有一个 token 生效,一旦刷新 token,前面生成的 token 都会失效,一般以用户 id 来做区分。
2. 多点登录
token 不做限制,一旦刷新 token,则当前 token 会失效。
注意:使用单点登录或者多点登录时,必须要开启黑名单,并且使用 hyperf 的缓存(建议使用 redis 缓存)。如果不开启黑名单,无法使 token 失效,生成的 token 会在有效时间内都可以使用(未更换证书或者 secret)。
3. 单点登录原理
JWT 有七个默认字段供选择。单点登录主要用到 jti 默认字段,jti 字段的值默认为用户 id。当生成 token 时,getToken 方法有一个 $isInsertSsoBlack 参数来控制是否会把前面生成的 token 都失效,默认是失效的,如果想不失效,设置为 false 即可。但是如果是调用 refreshToken 来刷新 token 或者调用 logout 注销 token,默认前面生成的 token 都会失效。
jwt 的生成的 token 加入黑名单时,会把用户 id 作为缓存的键,当前时间作为值,配置文件中的 blacklist_cache_ttl 作为缓存的失效时间。每次生成 token 或者刷新 token 时,会先从 token 中拿到签发时间和 jti 的值,根据 jti 值找到对应的缓存拿到时间,拿到时间后跟 token 的签发时间对比,如果签发时间小于等于拿到的时间值,则 token 判断为失效的。(jti 在单点登录中,存的值是用户 id)
4. 多点登录原理
多点登录跟单点登录差不多,唯一不同的是 jti 的值不是用户 id,而是一个唯一字符串,每次调用 refreshToken 来刷新 token 或者调用 logout 注销 token 会默认把请求头中的 token 加入到黑名单,而不会影响到别的 token。
5. token 不做限制原理
token 不做限制,在 token 有效的时间内都能使用,你只要把配置文件中的 blacklist_enabled 设置为 false 即可,即为关闭黑名单功能。
三、使用方法
1. 拉取依赖
1
| composer require phper666/jwt-auth
|
2. 发布配置
1
| php bin/hyperf.php jwt:publish --config
|
3. JWT 配置
去配置 config/autoload/jwt.php 文件或者在配置文件 .env 里配置
1 2 3 4
| # 务必改为你自己的字符串 JWT_SECRET=hyperf #token过期时间,单位为秒 JWT_TTL=60
|
更多的配置请到 config/autoload/jwt.php 查看
4、全局路由验证
在 config/autoload/middlewaress.php 配置文件中加入 jwt 验证中间件,所有的路由都会进行 token 的验证,例如:
1 2 3 4 5 6
| <?php return [ 'http' => [ Phper666\JwtAuth\Middleware\JwtAuthMiddleware::class ], ];
|
5、局部验证
在 config/routes.php 文件中,想要验证的路由加入 jwt 验证中间件即可,例如:
1 2 3 4 5
| <?php
Router::addGroup('/v1', function () { Router::get('/data', 'App\Controller\IndexController@getData'); }, ['middleware' => [Phper666\JwtAuth\Middleware\JwtAuthMiddleware::class]]);
|
6、注解的路由验证
请看官方文档:https://doc.hyperf.io/#/zh/middleware/middleware 在你想要验证的地方加入 jwt 验证中间件即可。
7、模拟登录获取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
| <?php
namespace App\Controller; use \Phper666\JwtAuth\Jwt; class IndexController extends Controller { # 模拟登录,获取token public function login(Jwt $jwt) { $username = $this->request->input('username'); $password = $this->request->input('password'); if ($username && $password) { //这里应为没有做auth的登录认证系统,为了展示随便写点数据 $userData = [ 'uid' => 1, 'username' => 'xx', ]; //获取Token $token = (string)$jwt->getToken($userData); //返回响应的json数据 return $this->response->json(['code' => 0, 'msg' => '获取token成功', 'data' => ['token' => $token]]); }
return $this->response->json(['code' => 0, 'msg' => '登录失败', 'data' => []]); }
# http头部必须携带token才能访问的路由 public function getData() { return $this->response->json(['code' => 0, 'msg' => 'success', 'data' => ['a' => 1]]); } }
|
注意:暂时不支持传入用户对象获取 token ,后期会支持
8、路由
1 2 3 4 5 6 7 8
| <?php # 登录 Router::post('/login', 'App\Controller\IndexController@login');
# 获取数据 Router::addGroup('/v1', function () { Router::get('/data', 'App\Controller\IndexController@getData'); }, ['middleware' => [Phper666\JwtAuth\Middleware\JwtAuthMiddleware::class]]);
|
9、鉴权
在需要鉴权的接口,请求该接口时在 HTTP 请求的头部加入
Authorization Bearer token
10、演示
获取 Token

将刚刚获取到的 Token 放在如下位置,请求 /v1/data 接口,如下图:

11、例子文件
路由文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php
declare(strict_types=1); /** * This file is part of Hyperf. * * @link https://www.hyperf.io * @document https://doc.hyperf.io * @contact group@hyperf.io * @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE */
use Hyperf\HttpServer\Router\Router;
// 登录 Router::post('/login', 'App\Controller\IndexController@login');
// 获取数据 Router::addGroup('/v1', function () { Router::get('/refresh-token', 'App\Controller\IndexController@refreshToken'); Router::get('/logout', 'App\Controller\IndexController@logout'); Router::get('/data', 'App\Controller\IndexController@getData'); }, ['middleware' => [Phper666\JwtAuth\Middleware\JwtAuthMiddleware::class]]);
|
JWT验证文件
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 104 105 106 107 108 109 110 111 112 113 114 115
| <?php
declare(strict_types=1); /** * This file is part of Hyperf. * * @link https://www.hyperf.io * @document https://doc.hyperf.io * @contact group@hyperf.io * @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE */
namespace App\Controller;
use Phper666\JwtAuth\Jwt; use Psr\Container\ContainerInterface;
class IndexController extends Controller { protected $container;
protected $jwt;
/** * 通过构造函数注入JWT. * * IndexController constructor. * * @param ContainerInterface $container * @param Jwt $jwt */ public function __construct(Jwt $jwt) { $this->jwt = $jwt; }
/** * 模拟登录. * * @return \Psr\Http\Message\ResponseInterface */ public function login() { $username = $this->request->input('username'); $password = $this->request->input('password'); if ($username && $password) { $userData = [ 'uid' => 1, // 如果使用单点登录,必须存在配置文件中的sso_key的值,一般设置为用户的id 'username' => 'xx', ]; $token = $this->jwt->getToken($userData); $data = [ 'code' => 0, 'msg' => 'success', 'data' => [ 'token' => (string) $token, 'exp' => $this->jwt->getTTL(), ], ]; return $this->response->json($data); } return $this->response->json(['code' => 0, 'msg' => '登录失败', 'data' => []]); }
/** * 刷新token,http头部必须携带token才能访问的路由. * * @throws \Psr\SimpleCache\InvalidArgumentException * @return \Psr\Http\Message\ResponseInterface */ public function refreshToken() { $token = $this->jwt->refreshToken(); $data = [ 'code' => 0, 'msg' => 'success', 'data' => [ 'token' => (string) $token, 'exp' => $this->jwt->getTTL(), ], ]; return $this->response->json($data); }
/** * 注销token,http头部必须携带token才能访问的路由. * * @throws \Psr\SimpleCache\InvalidArgumentException * @return string */ public function logout() { if ($this->jwt->logout()) { return '退出登录成功'; }; return '退出登录失败'; }
/** * http头部必须携带token才能访问的路由. * * @return \Psr\Http\Message\ResponseInterface */ public function getData() { $data = [ 'code' => 0, 'msg' => 'success', 'data' => [ 'cache_time' => $this->jwt->getTokenDynamicCacheTime(), // 获取token的有效时间,动态的 ], ]; return $this->response->json($data); } }
|
12、获取解析后的token数据
提供了一个方法 getParserData 来获取解析后的token数据。 例如:$this->jwt->getParserData()
13、建议
目前 jwt 抛出的异常目前有两种类型
Phper666\JwtAuth\Exception\TokenValidException 异常为 token 验证失败的异常,会抛出 401
Phper666\JwtAuth\Exception\JWTException,TokenValidException JWTException异常会抛出 500
最好自己在项目异常重新返回错误信息
四、结语
1、心得:
目前hyperf社区的生态还需要更多人去维持,希望大家能够发出更多的教程,有问题都可以在社区中提问,这样慢慢的很多问题的解决办法都能在社区中找到,社区也会越来越繁荣!
2、鸣谢:
最后感谢这个组件的开发者:phper666
他的个人博客为:https://www.liyuzhao.cn