Hyperf使用hyperf-ext/jwt

1,701 阅读3分钟

做一件事,无论大小,倘无恒心,是很不好的。而看一切太难,固然能使人无成,但若看得太容易,也能使事情无结果。

前言

在使用Hyperf的时候,想使用jwt权限认证,发现官方有推荐的几个完整auth包,也有单独封装的jwt组件,进去看了一下单独的jwt组件,发现他的文档使用方法并没有写的让人很明白,可能更多是想让大家使用hyperf-ext/auth这个组件。但是我们也可以基于hyperf-ext/jwt自己封装一下,以下为我摸索的使用方法。

安装

直接简单使用composer命令安装即可:

composer require hyperf-ext/jwt

配置

安装完成后,可以使用命令发布配置文件,文件位于config/autoload/jwt.php

php bin/hyperf.php vendor:publish hyperf-ext/jwt

具体的各种配置对应的会在它的文档里写清楚,这里不多做赘述。只说明一些用上的,比如

image.png 配置里面的密钥有两种方式,一种是随机字符串,一种是公钥私钥,两种方法都提供了对应的辅助命令帮你生成,二选一就好了,我这里选择了公私钥,所以执行一下命令

php bin/hyperf.php gen:jwt-keypair

然后按照命令台选择对应的选项(好像选择后要按回车确认),便会在.env文件上面生成对应的参数

image.png

然后便是jwt有效时间和刷新时间的配置,这个具体按照业务来,我为了方便调试,便设置了一些较短的时间

image.png 同理在.env文件中配置

JWT_TTL=20
JWT_REFRESH_TTL=120

然后需要配置一下redis

image.png

因为黑名单存储使用了缓存,当时我没有配置redis导致有问题,同时顺便安装一下composer require hyperf/cache

基本配置完成后,便可以开始使用了!

使用

官方文档里面的使用方法仅仅下面一段

<?php

declare(strict_types=1);

use HyperfExt\Jwt\Contracts\JwtFactoryInterface;
use HyperfExt\Jwt\Contracts\ManagerInterface;

class SomeClass
{
    /**
     * 提供了对 JWT 编解码、刷新和失活的能力。
     *
     * @var \HyperfExt\Jwt\Contracts\ManagerInterface
     */
    protected $manager;

    /**
     * 提供了从请求解析 JWT 及对 JWT 进行一系列相关操作的能力。
     *
     * @var \HyperfExt\Jwt\Jwt
     */
    protected $jwt;

    public function __construct(
        ManagerInterface $manager,
        JwtFactoryInterface $jwtFactory
    ) {
        $this->manager = $manager;
        $this->jwt = $jwtFactory->make();
    }
}

比较模糊也不知道具体怎么使用,于是去看了一下两个类,发现其实我们使用只需要用上\HyperfExt\Jwt\Jwt这个类就好了,因为里面已经封装了生成token的方法public function fromSubject(JwtSubjectInterface $subject): string,看一下

image.png 但这个方法需要传进一个实现JwtSubjectInterface接口的对象,那就简单了,我们可以新建一个类实现这个接口便可以,这个接口需要实现两个方法

image.png

getJwtIdentifier()是为jwt里面的sub参数赋值,要求唯一,你可以按照你想要的方式生成一个唯一值就好了,我这里便用用户主键ID作为唯一值就好了

getJwtCustomClaims()这个方法是给payload添加我们自己想要补充的数据,它最后会在jwt默认的参数数组合并,生成最终的payload,了解完后,便开始写代码~

新建实现JwtSubjectInterface的类CustomerPayload.php

新建CustomerPayload.php,代码如下

<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace App\Helpers;

use Hyperf\Snowflake\IdGeneratorInterface;
use Hyperf\Utils\ApplicationContext;
use HyperfExt\Jwt\Contracts\JwtSubjectInterface;

class CustomerPayload implements JwtSubjectInterface
{
    // 定义一个主键
    protected string|int $primaryKey;

    // 定义返回体
    protected array $customerPayload;

    public function __construct(string|int $primaryKey, array $customerPayload)
    {
        $this->primaryKey = $primaryKey;
        $this->customerPayload = $customerPayload;
    }

    public function getJwtIdentifier()
    {
        // TODO: Implement getJwtIdentifier() method.
        /*
         * 如果你是有自己的算法生成唯一值,
         * 可以不用像我这样传递一个值进来,
         * 而是直接在这里写对应的方法返回就好
         * 比如我使用hyperf生成雪花ID
         * 如此上面的primaryKey便可删除
         * 这些不是重点,你可以自己按照自己的方式来
         */
        // $container = ApplicationContext::getContainer();
        // $generator = $container->get(IdGeneratorInterface::class);
        // return $generator->generate();
        return $this->primaryKey;
    }

    public function getJwtCustomClaims(): array
    {
        // TODO: Implement getJwtCustomClaims() method.
        return $this->customerPayload;
    }
}

模拟登录获取token

写一个登录方法,模拟登录,然后生成对应的token返回

public function login(): \Psr\Http\Message\ResponseInterface
{
    // 模拟登录后获取的用户信息
    $userInfo = [
        'user_id' => 1,
        'username' => 'qiuyier',
    ];

    // 获取一个JwtSubjectInterface对象
    $payloadClass = new CustomerPayload(1, ['data' => $userInfo]);

    // 获取一个jwt对象
    $jwtInterface = make(JwtFactoryInterface::class);
    $jwt = $jwtInterface->make();

    // 调用fromSubject()方法获取token
    $token = $jwt->fromSubject($payloadClass);

    return $this->response->json([
        'code' => 1,
        'msg' => 'success',
        'data' => [
            'expire' => Carbon::now()->getTimestamp() + config('jwt_expire'),
            'token' => $token,
        ],
    ]);
}

调用接口查看一下

image.png 由上图可以看到我们成功获取到了token

验证token信息

验证token信息同样只需要使用\HyperfExt\Jwt\Jwt这个类,便能简单验证token时效性,以及刷新token和获取token的内容,以下写法只是作为测试时方便使用,正常验证token应该是写在中间件里。

public function checkToken(): \Psr\Http\Message\ResponseInterface
{
    try {
        // 获取一个jwt对象
        $jwtInterface = make(JwtFactoryInterface::class);
        $jwt = $jwtInterface->make();

        // 检查判断token时效性,如果有效则返回payload,无效则返回false
        $payload = $jwt->check(true);

        /*
         * 这一部分为无感刷新token,
         * 即通过在配置中的refresh_ttl的有效期内,
         * 对token进行刷新,获得新的token
         * 通过和前端约定的code判断是否更新旧token,
         * 过了刷新时效则需要重新登录,
         * 这部分自由选择,只是作为一个简单例子
         */
        if (! $payload) {
            /**
             * 忽视过期情况获取旧的payload数组,
             * 用于刷新token时保留原有的自定义数组.
             */
            $payload = $jwt->getPayload(true);

            /** setCustomClaims把原有的自定义数组传进去,
             * 否则刷新token后则会丢失这一部分自定义数组,
             * refresh为刷新token.
             */
            $token = $jwt->setCustomClaims(['data' => $payload['data']])->refresh();
            return $this->response->json([
                'code' => 10086,
                'msg' => 'token expire',
                'data' => [
                    'expire' => Carbon::now()->getTimestamp() + config('jwt_expire'),
                    'token' => $token,
                ],
            ])->withStatus(401);
        }

        return $this->response->json([
            'code' => 1,
            'msg' => 'success',
            'data' => $payload['data'],
        ]);
    } catch (JwtException $e) {
        // 抛出异常
        return $this->response->json([
            'code' => 0,
            'msg' => $e->getMessage(),
            'data' => null,
        ]);
    }
}

调用一下接口,能成功获取我们自定义数组的内容,结果如图:

image.png

打印一下获取的payload,内容如下: image.png

如果需要token过期自动刷新,则返回如下图:

image.png

这时如果我们再用旧token请求便会告知旧token被拉入黑名单了,如图:

image.png

如果超过刷新时长再去请求,则会告知token失效且不能再刷新,如图: image.png

至此hyperf-ext/jwt基本使用已经介绍完毕,感谢阅读~