Hyperf 使用 JWT 做接口认证
一、声明:
1、本文来自:
https://packagist.org/packages/phper666/jwt-auth
2、迁移原因:
hyperf
目前在网上没什么接口认证教程,正好本人在 composer
组件库中看到该组件,认为做的比较不错,所以搬迁到 learnku
的 hyperf
社区供大家学习参考!
二、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
将刚刚获取到的 Token
放在如下位置,请求 /v1/data
接口,如下图:
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
在payload再添加一个身份标识的字段就可以实现多用户的登录了。
感谢分享
token过期了,可以刷新获取新的token吗?比如,token的有效期是2个小时,登录后,用户2个小时后再进入,可以通过某些参数刷新token吗,这样可以达到记住密码的目的
是可以刷新token的,在包中的文档有说明,写法是这样的
@jqcool
验证完token之后怎么获取解析后的 token 数据,提供的getParserData直接调用会报错
能不能jwt中间件验证成功后把用户数据解析放到request里,后续需要直接request拿就可以了
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:
报这个错,是什么 原因
use \Phper666\JwtAuth\Jwt; 应该改为 use \Phper666\JWTAuth\JWT; 否则会提示类找不到
默认是存哪里的?怎么改成redis?