Hyperf 使用 JWT

总结

  • 安装 hyperf-ext/authhyperf-ext/jwt 并配置好
  • 修改 auth 的配置文件
    • guard 默认改为 api
    • api 的驱动使用 JwtGuard,提供者provider的模型改为自己的user用户模型
  • 修改 user用户模型,实现俩个接口 JwtSubjectInterface AuthenticatableInterface
    • 使用 use HyperfExt\Auth\Authenticatable; 实现AuthenticatableInterface
    • 添加 getJwtIdentifier() getJwtCustomClaims()实现JwtSubjectInterfacegetJwtIdentifier()方法要返回主键ID
  • 创建中间件
    • 使用JwtFactory类的make(),获得$jwt对象
    • 使用 $jwt->checkOrFail() 验证Token,失败返回response
  • 使用
    • $auth = make(AuthManagerInterface::class)->guard('api');
    • $auth->login(UserModel);
    • $auth->user();

安装一些组件

经评论提醒 加上hyperf-ext/hashing 此组件为 hyperf-ext/auth 依赖

composer require hyperf-ext/auth
composer require hyperf-ext/hashing
composer require hyperf-ext/jwt

发布配置

php bin/hyperf.php vendor:publish hyperf-ext/auth
php bin/hyperf.php vendor:publish hyperf-ext/hashing
php bin/hyperf.php vendor:publish hyperf-ext/jwt
# 生成 jwt 密钥
php bin/hyperf.php gen:jwt-secret

配置 auth

修改默认guard(可不改,后面使用时需指定),将api使用的provider的模型改为你的用户模型

'default' => [
    'guard' => 'api',
]

'providers' => [
    'users' => [
        'driver' => \HyperfExt\Auth\UserProviders\ModelUserProvider::class,
        'options' => [
            'model' => App\Model\User::class, # 用户模型
            'hash_driver' => 'bcrypt',
        ],
    ],
]

config/autoload/auth.php

添加auth助手函数

新建一个文件写入如下代码,方便使用

if (!function_exists('auth')) {
    /**
     * Auth认证辅助方法
     * @param string|null $guard
     * @return \HyperfExt\Auth\Contracts\GuardInterface|\HyperfExt\Auth\Contracts\StatefulGuardInterface|\HyperfExt\Auth\Contracts\StatelessGuardInterface
     */
    function auth(string $guard = null)
    {
        if (is_null($guard)) $guard = config('auth.default.guard');
        return make(AuthManagerInterface::class)->guard($guard);
    }
}

app/Functions.php

将上面文件加入到 composer 的自动加载中

"autoload": {
    "files": [
        "app/Functions.php"
    ]
}

composer.json

修改user模型

实现 jwt,auth 的接口
class User extends Model implements JwtSubjectInterface,AuthenticatableInterface

增加 auth 支持
use Authenticatable;

增加 jwt接口需要的方法
public function getJwtCustomClaims(): array
{
    return [
            'guard' => 'api'    // 添加自定义信息
        ];
}

public function getJwtIdentifier()
{
    return $this->getKey();
}

新建一个 ApiResponse Trait

方便处理接口返回内容,或者可以自己手动返回
ResponseInterface
withBody(Stream)

<?php
declare(strict_types=1);
namespace App\Traits;

use Hyperf\HttpMessage\Stream\SwooleStream;
use Hyperf\Utils\Codec\Json;
use Hyperf\Utils\Context;
use Hyperf\Utils\Contracts\Arrayable;
use Hyperf\Utils\Contracts\Jsonable;
use Psr\Http\Message\ResponseInterface;

/**
 * Trait ApiResponse
 * @author Colorado
 * @package App\Traits
 */
trait ApiResponse
{
    private $httpCode = 200;
    private $errorCode = 100000;
    private $errorMsg = '系统错误';
    private $headers = [];

    protected $response;

    /**
     * 成功响应
     * @param mixed $data
     * @return ResponseInterface
     */
    public function success($data): ResponseInterface
    {
        return $this->respond($data);
    }

    /**
     * 错误返回
     * @param int    $err_code    错误业务码
     * @param string $err_msg     错误信息
     * @param array  $data        额外返回的数据
     * @return ResponseInterface
     */
    public function error(int $err_code = null,string $err_msg = null, array $data = []): ResponseInterface
    {
        return $this->setHttpCode($this->httpCode == 200 ? 400 : $this->httpCode)
            ->respond([
                'err_code' => $err_code ?? $this->errorCode,
                'err_msg' => $err_msg ?? $this->errorMsg,
                'data' => $data
            ]);
    }

    /**
     * 设置http返回码
     * @param int $code    http返回码
     * @return $this
     */
    final public function setHttpCode(int $code = 200): self
    {
        $this->httpCode = $code;
        return $this;
    }

    /**
     * 设置返回头部header值
     * @param string $key
     * @param        $value
     * @return $this
     */
    public function addHttpHeader(string $key, $value): self
    {
        $this->headers += [$key => $value];
        return $this;
    }

    /**
     * 批量设置头部返回
     * @param array $headers    header数组:[key1 => value1, key2 => value2]
     * @return $this
     */
    public function addHttpHeaders(array $headers = []): self
    {
        $this->headers += $headers;
        return $this;
    }

    /**
     * @param null|array|Arrayable|Jsonable|string $response
     * @return ResponseInterface
     */
    private function respond($response): ResponseInterface
    {
        if (is_string($response)) {
            return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream($response));
        }

        if (is_array($response) || $response instanceof Arrayable) {
            return $this->response()
                ->withAddedHeader('content-type', 'application/json')
                ->withBody(new SwooleStream(Json::encode($response)));
        }

        if ($response instanceof Jsonable) {
            return $this->response()
                ->withAddedHeader('content-type', 'application/json')
                ->withBody(new SwooleStream((string)$response));
        }

        return $this->response()->withAddedHeader('content-type', 'text/plain')->withBody(new SwooleStream((string)$response));
    }

    /**
     * 获取 Response 对象
     * @return mixed|ResponseInterface|null
     */
    protected function response(): ResponseInterface
    {
        $response = Context::get(ResponseInterface::class);
        foreach ($this->headers as $key => $value) {
            $response = $response->withHeader($key, $value);
        }
        return $response;
    }
}

创建验证中间件

hyperf的中间件和laravel的中间件实现方式不同,需要创建新的中间件
验证失败时,返回错误 response

php bin/hyperf.php gen:middleware JwtMiddleware
<?php

declare(strict_types=1);

namespace App\Middleware;

use App\Traits\ApiResponse;
use Hyperf\Di\Annotation\Inject;
use HyperfExt\Jwt\Exceptions\TokenExpiredException;
use HyperfExt\Jwt\JwtFactory;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class JwtMiddleware implements MiddlewareInterface
{
    use ApiResponse;
    /**
     * @var ContainerInterface
     */
    protected $container;
    
    /**
     * 注入 JwtFactory
     * @Inject
     * @var JwtFactory
     */
    private $jwtFactory;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $jwt = $this->jwtFactory->make();

        try {
            $jwt->checkOrFail();
        } catch (\Exception $exception) {
            if (! $exception instanceof TokenExpiredException) {
                return $this->error(40000,$exception->getMessage());
            }
            //  尝试自动刷新 token
            try {
                $token = $jwt->getToken();

                // 刷新token
                $new_token = $jwt->getManager()->refresh($token);

                // 解析token载荷信息
                $payload = $jwt->getManager()->decode($token, false, true);

                // 旧token加入黑名单
                $jwt->getManager()->getBlacklist()->add($payload);

                // 一次性登录,保证此次请求畅通
                auth($payload->get('guard') ?? config('auth.default.guard'))
                    ->onceUsingId($payload->get('sub'));

                return $handler->handle($request)->withHeader('authorization', 'bearer ' . $new_token);
            } catch (\Exception $exception) {
                //    Token 验证失败
                return $this->setHttpCode(401)->error(40001,$exception->getMessage());
            }
        }

        return $handler->handle($request);
    }
}

使用中间件

配置路由 或者 注解

OPTIONS = ['middleware' => [ JwtMiddleware::class ]];

Router::get(PATH,FALLBACK,OPTIONS);
Router::addGroup(PREFIX,FALLBACK,OPTIONS);
Router::addRoute(FUNCTION,PATH,FALLBACK,OPTIONS);

单个

/**
 * @Middleware(FooMiddleware::class)
 */

多个

/**
 * @Middlewares({
 *     @Middleware(FooMiddleware::class),
 *     @Middleware(BarMiddleware::class)
 * })
 */

在控制器中使用 auth

$token = auth()->login($User::first());
$user = auth()->user();
$userid = auth()->id();

转载请注明简书或博客地址 https://saopanda.top/php/16684d5d.html

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容