coposer地址:https://packagist.org/packages/tymon/jwt-auth
github地址:https://github.com/tymondesigns/jwt-auth
使用文档:https://jwt-auth.readthedocs.io/en/develop/
从项目名称看,它包含 jwt 和 auth 两个功能,jwt 解出subject的值,auth 使用这个值去数据库查找是否存在,而 auth 不是必须的,你也可以自己去查一下。
Laravel-5.7开启jwt-auth
安装依赖: composer require tymon/jwt-auth
在config 目录下生成 jwt.php 配置文件
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
最小配置(env)
bash
JWT_SECRET=xxxxxxxxxxxxx
JWT_TTL=525600
修改User模型,实现 Tymon\JWTAuth\Contracts\JWTSubject 接口
php
/**
* Get the identifier that will be stored in the subject claim of the JWT.
*
* @return mixed
*/
public function getJWTIdentifier()
{
// 设置 sub 的值,此处返回 user 模型的主键ID
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* @return array
*/
public function getJWTCustomClaims()
{
// 自定义 jwt claim 的键值对,非必须
return [];
}
jwt.php 配置文件中的 public和private是非对称加密的秘钥,是jwt的另一种用途,此处不涉及。
在 vendor\tymon\jwt-auth\src\Claims\Factory.php 中定义了各个 claim 的生成算法
php
public function iss() // Issuer
{
return $this->request->url();
}
public function iat() // Issued At
{
return Utils::now()->getTimestamp();
}
public function exp() // Expiration
{
return Utils::now()->addMinutes($this->ttl)->getTimestamp();
}
public function nbf() // Not Before
{
return Utils::now()->getTimestamp();
}
public function jti() // JWT Id
{
return Str::random(); // 默认长度16
}
关于 claim 中的 prv 字段,其说明如下
php
/*
|--------------------------------------------------------------------------
| Lock Subject
|--------------------------------------------------------------------------
|
| This will determine whether a `prv` claim is automatically added to
| the token. The purpose of this is to ensure that if you have multiple
| authentication models e.g. `App\User` & `App\OtherPerson`, then we
| should prevent one authentication request from impersonating another,
| if 2 tokens happen to have the same id across the 2 different models.
|
| Under specific circumstances, you may want to disable this behaviour
| e.g. if you only have one authentication model, then you would save
| a little on token size.
|
*/
'lock_subject' => true,
vendor\tymon\jwt-auth\src\JWT.php
php
/**
* Get the claims associated with a given subject.
*
* @param \Tymon\JWTAuth\Contracts\JWTSubject $subject
*
* @return array
*/
protected function getClaimsForSubject(JWTSubject $subject)
{
return array_merge([
'sub' => $subject->getJWTIdentifier(),
], $this->lockSubject ? ['prv' => $this->hashSubjectModel($subject)] : []);
}
/**
* Hash the subject model and return it.
*
* @param string|object $model
*
* @return string
*/
protected function hashSubjectModel($model)
{
return sha1(is_object($model) ? get_class($model) : $model);
}
/**
* Check if the subject model matches the one saved in the token.
*
* @param string|object $model
*
* @return bool
*/
public function checkSubjectModel($model)
{
if (($prv = $this->payload()->get('prv')) === null) {
return true;
}
return $this->hashSubjectModel($model) === $prv;
}
例如
bash
get_class(User): App\Model\User
sha1('App\Model\User'): f6b71549db8c2c42b75827aa44f02b7ee529d24d
所以 prv 的作用就是标识 sub 是来自哪个模型,比如 \App\Model\User 和 \App\Model\OtherPerson,因此在解析 token 的时候,得到 sub 之后,还要调用 checkSubjectModel 方法来鉴别具体是哪个模型,才知道要去查哪个表,当然 prv 是可选的,视情况而定。
php
$id = $auth->getPayload()->get('sub');
$auth->checkSubjectModel(\App\Model\User::class)
$auth->checkSubjectModel(\App\Model\OtherPerson::class)
生成 jwt token
php
public function createToken($id)
{
$user = new User();
$user->id = $id;
return \JWTAuth::claims([new Custom('iss', 'BK-JTW')])->fromUser($user);
}
解析jwt token
php
public function checkToken(Request $request, JWTAuth $auth)
{
try {
$token = $request->input('token', '');
if (!$token) {
return $this->ajaxError('params error');
}
$auth->setRequest($request);
if (!$auth->parser()->hasToken()) {
return $this->ajaxError('token not found', '');
}
$auth->parseToken();
$id = $auth->getPayload()->get('sub');
$userInfo = $this->repository->getUserById($id);
if ($userInfo) {
return $this->ajaxSuccess('success', $userInfo);
} else {
return $this->ajaxError('customer not found');
}
} catch (Exception $e) {
return $this->ajaxError($e->getMessage(), "");
}
}
在 $auth->getPayload()方法中就已经对 token 进行了校验,如果 signature 校验失败会抛出 Tymon\\JWTAuth\\Exceptions\\TokenInvalidException异常Token Signature could not be verified.
jwt 解析网站 https://www.jwt.io/

它能解析出 Header 和 Payload,但是由于不知道 jwt_secret,所以校验不通过。
退出登录(invalidate token)源码解析
php
public function logout(Request $request, JWTAuth $auth)
{
try {
$token = $request->input('token', '');
$auth->setToken($token);
$result = $auth->invalidate();
return $this->ajaxSuccess('success', $result);
} catch (Exception $e) {
return $this->ajaxError($e->getMessage(), '');
}
}
如果 signature 校验失败会抛出 Tymon\\JWTAuth\\Exceptions\\TokenInvalidException异常Token Signature could not be verified.
退出登录本质是将 jti 值加入到 Blacklist,会产生3个key
bash
dbuc_cache:tag:tymon.jwt:key
s:22:"69882bbf2ec4b334764625";
dbuc_cache:69882bbf2ec4b334764625:standard_ref
1 dbuc_cache:33f80c37acd56e44b79280b77f88dc0c45f3cf07:yJK1TEqpBEys5FSJ
dbuc_cache:33f80c37acd56e44b79280b77f88dc0c45f3cf07:yJK1TEqpBEys5FSJ
a:1:{s:11:"valid_until";i:1770531775;}
源码vendor\tymon\jwt-auth\src\JWT.php
bash
storage: Tymon\JWTAuth\Providers\Storage\Illuminate
cache: Illuminate\Cache\RedisTaggedCache
store: Illuminate\Cache\RedisStore
cache tag: tymon.jwt
tagKey: 'tag:'.$tagName.':key'
cache prefix: 默认为 {APP_NAME}_cache
3个key的关系如下
namespace: 69882bbf2ec4b334764625,其生成规则为 str_replace('.', '', uniqid('', true))
sha1(namespace): 33f80c37acd56e44b79280b77f88dc0c45f3cf07
yJK1TEqpBEys5FSJ 为 jwt claim 中的 jti 值
itemKey: 33f80c37acd56e44b79280b77f88dc0c45f3cf07:yJK1TEqpBEys5FSJ
tag的存在是为了方便管理keys,为key打上tag,本质上就是为key增加了一个前缀,减小管理的粒度。
在 vendor\tymon\jwt-auth\src\Blacklist.php 中已经规定了将 token 的 jti 值加入到 blacklist,并设置解封时间,这个时间就是 token 的 exp 字段的值,如果没有设置 exp ,那就是永久封禁。
valid_until 为添加时间,valid_until + TTL = exp
这就要求 jti 值必须是唯一的,jti(JWT ID) 是一个可选的标准声明(registered claim),用于唯一标识一个 JWT 令牌,主要目的是防止重放攻击(replay attack)。同一个 user_id 在生成 token 的时候会得到不同的 jti 值,所以,退出操作只针对单个的 token