一、声明:
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、拉取依赖
composer require phper666/jwt-auth
2、发布配置
php bin/hyperf.php jwt:publish --config
3、jwt配置
去配置 config/autoload/jwt.php 文件或者在配置文件 .env 里配置
| 12
 3
 4
 
 | # 务必改为你自己的字符串JWT_SECRET=hyperf
 #token过期时间,单位为秒
 JWT_TTL=60
 
 | 
更多的配置请到 config/autoload/jwt.php 查看
4、全局路由验证
在 config/autoload/middlewaress.php 配置文件中加入 jwt 验证中间件,所有的路由都会进行 token 的验证,例如:
| 12
 3
 4
 5
 6
 
 | <?phpreturn [
 'http' => [
 Phper666\JwtAuth\Middleware\JwtAuthMiddleware::class
 ],
 ];
 
 | 
5、局部验证
在 config/routes.php 文件中,想要验证的路由加入 jwt 验证中间件即可,例如:
| 12
 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
| 12
 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、路由
| 12
 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、例子文件
路由文件:
| 12
 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验证文件
| 12
 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