hyperf-jwt登录限制登录次数

composer require firebase/php-jwt:*

登录验证类

<?php

declare(strict_types=1);

namespace App\Logic\Admin;

use App\Annotation\LoginAnnotation;
use App\Constants\Constants;
use App\Exception\EmptyException;
use App\Exception\InvalidConfigException;
use App\Exception\LoginException;
use App\Exception\StatusException;
use App\Exception\UserNotFoundException;
use App\Facade\Redis;
use App\Service\AuthService;
use App\Service\UserService;
use App\Util\Payload;
use App\Util\Prefix;
use App\Util\Token;

class LoginLogic
{
    /**
     * @LoginAnnotation()
     * 登录redis限制错误次数
     * @throws EmptyException
     * @throws InvalidConfigException
     * @throws UserNotFoundException
     *
     * @return array
     */
    public function login(string $username, string $password): array
    {
        $userLogic = di(UserLogic::class);

        $user = di(UserService::class)->getUserByName($username);

        if (empty($user)) {
            throw new UserNotFoundException('账号不存在!', 1);
        }
        if ((int) $user->status === 0) {
            throw new StatusException('账号已被禁用,请联系管理员!', 1);
        }

        $max_count = 5;//可重试次数

//        $redis = Redis::getInstance();
        $redis = Redis::instance();

        $key = Prefix::getLoginErrCount($username);

//        var_dump(\App\Facade\Redis::get($key)); //直接使用静态方法调用也是可以的。

        $login_err_count = $redis->get($key);
        if ($login_err_count === false) {
            $login_err_count = 0;
            $redis->set($key, $login_err_count, 3600);
        }

        if ($login_err_count >= $max_count) {
            throw new LoginException('尝试次数达到上限,锁定一小时内禁止登录!', 1);
        }
        //判断连续输错次数  可重试5次
        if (! $userLogic->verifyPassword($password, $user->password)) {
            //错误次数+1
            $redis->incr($key);
            $login_err_count++;
            $diff = $max_count - $login_err_count;

            if ($diff) {
                $error = "账号或密码错误,还有{$diff}次尝试机会!";
            } else {
                $error = '尝试次数达到上限,锁定一小时内禁止登录!';
            }

            throw new LoginException($error, 1);
        }
        //清除错误次数
        $redis->del($key);

        //查询角色名称
        $authService = di(AuthService::class);

        $auth = $authService->info($user->role_id);
        if (! $auth) {
            throw new EmptyException('当前用户角色不存在,请联系管理员!');
        }
        if ((int) $auth->status !== Constants::STATUS_ACTIVE) {
            throw new StatusException('当前用户角色被禁用,请联系管理员!');
        }

        $app_name = config('app_name', '');

        $app_key = config('app_key', '');

        if (empty($app_key) || empty($app_name)) {
            throw new InvalidConfigException('配置有误!', 1);
        }

        $cur_time = time();

        $payload = new Payload();

        $payload['jti'] = uuid(16);
        $payload['iss'] = $app_name;
        $payload['sub'] = 'api.onetech.site';
        $payload['aud'] = 'api.onetech.site';
        $payload['ita'] = $cur_time;
        $payload['nbf'] = $cur_time;
        $payload['exp'] = $cur_time + 3600 * 24 * 10;
        $payload['scopes'] = Constants::SCOPE_ROLE;
        $payload['data'] = [
            'user_id' => $user->id,
            'user_name' => $user->username,
            'role_id' => $user->role_id,
            'role_name' => $auth->title,
        ];

        $accessToken = Token::instance();

        $token = $accessToken->createToken($payload);

        $payload['exp'] = $cur_time + 84300;
        $payload['scopes'] = Constants::SCOPE_REFRESH;

        $refresh_token = $accessToken->createToken($payload);

        return compact('token', 'refresh_token');
    }

    /**
     * @param $refresh
     *  刷新token
     * @throws \Exception
     *
     * @return array
     */
    public function refreshToken($refresh): array
    {
        $accessToken = Token::instance();

        $jwt = $accessToken->checkRefreshToken($refresh);

        $data = (array) ($jwt['data']);

        $app_name = config('app_name', '');

        $cur_time = time();

        $payload = new Payload();

        $payload['jti'] = uuid(16);
        $payload['iss'] = $app_name;
        $payload['sub'] = 'api.onetech.site';
        $payload['aud'] = 'api.onetech.site';
        $payload['ita'] = $cur_time;
        $payload['nbf'] = $cur_time;
        $payload['exp'] = $cur_time + 3600;
        $payload['scopes'] = Constants::SCOPE_ROLE;
        $payload['data'] = $data;

        $token = $accessToken->createToken($payload);

        $payload['exp'] = $cur_time + 84300;
        $payload['scopes'] = Constants::SCOPE_REFRESH;

        $refresh_token = $accessToken->createToken($payload);

        return compact('token', 'refresh_token');
    }
}

JWT类

<?php

declare(strict_types=1);

namespace App\Util;

use App\Constants\Constants;
use App\Exception\LoginException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Hyperf\Utils\Traits\StaticInstance;
use InvalidArgumentException;

class Token
{
    use StaticInstance;

    private static $alg = 'HS256';

    private static $app_key;

    private $user;

    private $user_id;

    public function __construct()
    {
        self::$app_key = config('app_key');
    }

    public function encode(array $payload): string
    {
        return JWT::encode($payload, self::$app_key, self::$alg);
    }

    /**
     * @return array
     *
     * @throws \Exception
     */
    public function decode(string $jwt): array
    {
        try {
            $decode = JWT::decode($jwt, self::$app_key, [self::$alg]);
            $user = $decode->data;
            $this->user = $user;
            $this->user_id = $user->user_id;
            return (array) $decode;
        } catch (ExpiredException $exception) {
            //过期token
            throw new LoginException('token过期!', -1);
        } catch (InvalidArgumentException $exception) {
            //参数错误
            throw new LoginException('token参数非法!', -1);
        } catch (\UnexpectedValueException $exception) {
            //token无效
            throw new LoginException('token无效!', -1);
        }
    }

    /**
     * 创建token
     */
    public function createToken(Payload $payload): string
    {
        return $this->encode($payload->toArray());
    }

    /**
     * @throws \Exception
     */
    public function checkToken(string $token): Payload
    {
        if (empty($token)) {
            throw new LoginException('token不能为空!', -1);
        }

        $decode = $this->decode($token);

        if ($decode === null) {
            throw new LoginException('token无效!', -1);
        }

        $jwt = new Payload($decode);

        if ($jwt->scopes !== Constants::SCOPE_ROLE) {
            throw new LoginException('token参数非法!', -2);
        }

        return $jwt;
    }

    /**
     * @return array
     *
     * @throws \Exception
     */
    public function checkRefreshToken(string $refresh): array
    {
        if (empty($refresh)) {
            throw new LoginException('token不能为空!', -1);
        }
        $decode = $this->decode($refresh);

        if ($decode === null) {
            throw new LoginException('token无效!', -1);
        }

        $jwt = new Payload($decode);

        if ($jwt->scopes !== Constants::SCOPE_REFRESH) {
            throw new LoginException('refresh-token参数非法!', -2);
        }

        return $jwt->toArray();
    }

    /**
     * 刷新token
     *
     * @param $refresh
     *
     * @throws \Exception
     */
    public function refreshToken($refresh): string
    {
        if (empty($refresh)) {
            throw new LoginException('参数有误!');
        }

        $jwt = $this->decode($refresh);

        if ($jwt === null) {
            throw new LoginException('refresh-token参数有误!', -2);
        }

        if ($jwt['scopes'] !== Constants::SCOPE_REFRESH) {
            throw new LoginException('refresh-token参数非法!', -2);
        }

        return $jwt['data'];
    }

    public function getUserId(): int
    {
        return $this->user_id;
    }

    public function getUser(): object
    {
        return $this->user;
    }
}