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

代码已被折叠,点此展开

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

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

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

5年前 评论
犯二青年 (楼主) 5年前

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

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

是可以刷新 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

5年前 评论

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

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

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

5年前 评论
了然、 5年前

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
4年前 评论

报这个错,是什么 原因

4年前 评论

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

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

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

2年前 评论