Hyperf 使用 JWT 做接口认证

一、声明:

1、本文来自:

https://packagist.org/packages/phper666/jwt-auth

2、迁移原因:

hyperf 目前在网上没什么接口认证教程,正好本人在 composer 组件库中看到该组件,认为做的比较不错,所以搬迁到 learnkuhyperf 社区供大家学习参考!

二、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 里配置

# 务必改为你自己的字符串
JWT_SECRET=hyperf
#token过期时间,单位为秒
JWT_TTL=60

更多的配置请到 config/autoload/jwt.php 查看

4、全局路由验证

config/autoload/middlewaress.php 配置文件中加入 jwt 验证中间件,所有的路由都会进行 token 的验证,例如:

<?php
return [
    'http' => [
        Phper666\JwtAuth\Middleware\JwtAuthMiddleware::class
    ],
];

5、局部验证

config/routes.php 文件中,想要验证的路由加入 jwt 验证中间件即可,例如:

<?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/midd... 在你想要验证的地方加入 jwt 验证中间件即可。

7、模拟登录获取token

<?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、路由

<?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

Hyperf使用JWT做接口认证

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

Hyperf使用JWT做接口认证

11、例子文件

路由文件:

<?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验证文件

<?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

犯二青年
本帖已被设为精华帖!
本帖由系统于 6年前 自动加精
Image
讨论数量: 21

在payload再添加一个身份标识的字段就可以实现多用户的登录了。

6年前 评论
Image 犯二青年 (楼主) 6年前

token过期了,可以刷新获取新的token吗?比如,token的有效期是2个小时,登录后,用户2个小时后再进入,可以通过某些参数刷新token吗,这样可以达到记住密码的目的

6年前 评论
Image 犯二青年 (楼主) 6年前
Image jqcool (作者) 6年前

是可以刷新token的,在包中的文档有说明,写法是这样的

    /**
     * 刷新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);
    }

@jqcool

6年前 评论

验证完token之后怎么获取解析后的 token 数据,提供的getParserData直接调用会报错

6年前 评论
Image 犯二青年 (楼主) 6年前
Image SunSay (作者) 6年前
Image SunSay (作者) 6年前
Image 犯二青年 (楼主) 6年前
Image SunSay (作者) 6年前

能不能jwt中间件验证成功后把用户数据解析放到request里,后续需要直接request拿就可以了

6年前 评论
Image 了然、 6年前

RuntimeException: Error while decoding to JSON: Control character error, possibly incorrectly encoded in file /srv/webroot/php/xueyuan-api/vendor/lcobucci/jwt/src/Parsing/Decoder.php on line 36 Stack trace:

  1. RuntimeException->() /srv/webroot/php/xueyuan-api/vendor/lcobucci/jwt/src/Parsing/Decoder.php:36
  2. Lcobucci\JWT\Parsing\Decoder->jsonDecode() /srv/webroot/php/xueyuan-api/vendor/lcobucci/jwt/src/Parser.php:112
  3. Lcobucci\JWT\Parser->parseHeader() /srv/webroot/php/xueyuan-api/vendor/lcobucci/jwt/src/Parser.php:60
  4. Lcobucci\JWT\Parser->parse() /srv/webroot/php/xueyuan-api/vendor/phper666/jwt-auth/src/JWT.php:217
  5. Phper666\JWTAuth\JWT->getTokenObj() /srv/webroot/php/xueyuan-api/vendor/phper666/jwt-auth/src/JWT.php:171
  6. Phper666\JWTAuth\JWT->getParserData() /srv/webroot/php/xueyuan-api/runtime/container/proxy/App_Controller_AbstractController.proxy.php:51
  7. App\Controller\AbstractController->auth() /srv/webroot/php/xueyuan-api/app/Controller/IndexController.php:29
  8. App\Controller\IndexController->index() /srv/webroot/php/xueyuan-api/vendor/hyperf/http-server/src/CoreMiddleware.php:161
  9. Hyperf\HttpServer\CoreMiddleware->handleFound() /srv/webroot/php/xueyuan-api/vendor/hyperf/http-server/src/CoreMiddleware.php:113
    1. Hyperf\HttpServer\CoreMiddleware->process() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php:64
    2. Hyperf\Dispatcher\AbstractRequestHandler->handleRequest() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/HttpRequestHandler.php:26
    3. Hyperf\Dispatcher\HttpRequestHandler->handle() /srv/webroot/php/xueyuan-api/app/Middleware/JwtAuthMiddleware.php:43
    4. App\Middleware\JwtAuthMiddleware->process() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php:64
    5. Hyperf\Dispatcher\AbstractRequestHandler->handleRequest() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/HttpRequestHandler.php:26
    6. Hyperf\Dispatcher\HttpRequestHandler->handle() /srv/webroot/php/xueyuan-api/vendor/hyperf/validation/src/Middleware/ValidationMiddleware.php:83
    7. Hyperf\Validation\Middleware\ValidationMiddleware->process() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php:64
    8. Hyperf\Dispatcher\AbstractRequestHandler->handleRequest() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/HttpRequestHandler.php:26
    9. Hyperf\Dispatcher\HttpRequestHandler->handle() /srv/webroot/php/xueyuan-api/app/Middleware/AuthMiddleware.php:29
    10. App\Middleware\AuthMiddleware->process() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php:64
    11. Hyperf\Dispatcher\AbstractRequestHandler->handleRequest() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/HttpRequestHandler.php:26
    12. Hyperf\Dispatcher\HttpRequestHandler->handle() /srv/webroot/php/xueyuan-api/app/Middleware/CorsMiddleware.php:31
    13. App\Middleware\CorsMiddleware->process() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/AbstractRequestHandler.php:64
    14. Hyperf\Dispatcher\AbstractRequestHandler->handleRequest() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/HttpRequestHandler.php:26
    15. Hyperf\Dispatcher\HttpRequestHandler->handle() /srv/webroot/php/xueyuan-api/vendor/hyperf/dispatcher/src/HttpDispatcher.php:40
    16. Hyperf\Dispatcher\HttpDispatcher->dispatch() /srv/webroot/php/xueyuan-api/vendor/gemini/hyperf-router/src/HttpDispatcher.php:35
    17. Gemini\Router\HttpDispatcher->dispatch() /srv/webroot/php/xueyuan-api/vendor/hyperf/http-server/src/Server.php:116
5年前 评论

报这个错,是什么 原因

5年前 评论

use \Phper666\JwtAuth\Jwt; 应该改为 use \Phper666\JWTAuth\JWT; 否则会提示类找不到

4年前 评论
Image jiangcanning (作者) 4年前
Image 犯二青年 (楼主) 4年前

默认是存哪里的?怎么改成redis?

3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!