一、什么是JWT
1. 简介
JWT(JSON Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。
简单的说,JWT就是一种Token的编码算法,服务器端负责根据一个密码和算法生成Token,然后发给客户端,客户端只负责后面每次请求都在HTTP header里面带上这个Token,服务器负责验证这个Token是不是合法的,有没有过期等,并可以解析出subject和claim里面的数据。
2. JWT的组成
第一部分为头部(header),第二部分我们称其为载荷(payload),第三部分是签证(signature)。【中间用 . 分隔】
一个标准的JWT生成的token格式如下: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjRmMWcyM2ExMmFhMTEifQ.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiNGYxZzIzYTEyYWExMSIsImlhdCI6MTY0MDE3MTA1MywibmJmIjoxNjQwMTcxMDU0LCJleHAiOjE2NDAxNzQ2NTMsInVpZCI6MjAsInVzZXJuYW1lIjoiTWFrZSJ9.bysUwNIyhqqEyL0JecSHdplSTfE6G6zuCsrAn6eyrQM
使用https://jwt.io/这个网站对JWT Token进行解析的结果如下
3. JWT验证流程和特点
验证流程:
① 在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串() ② 在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串 ③ 使用在header中声明的加密算法和每个项目随机生成的secret来进行加密, 把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。 ④ 解密的时候,只要客户端带着JWT来发起请求,服务端就直接使用secret进行解密。
特点:
① 三部分组成,每一部分都进行字符串的转化 ② 解密的时候没有使用数据库,仅仅使用的是secret进行解密 ③ JWT的secret千万不能泄密! ④ 不依赖数据库,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。
GitHub: https://github.com/lcobucci/jwt 官方文档:https://jwt.io/ 更多JWT介绍可参考: https://blog.csdn.net/xiaodong_526/article/details/106979452?spm=1001.2014.3001.5502 https://blog.csdn.net/u013308504/article/details/90374287 http://majiameng.com/article/2824.html
二、相关问题
- JWT Token需要持久化在redis、Memcached中吗?
不应该这样做,无状态的jwt变成了有状态了,背离了JWT通过算法验证的初心。 - 在退出登录时怎样实现JWT Token失效呢?
退出登录, 只要客户端端把Token丢弃就可以了,服务器端不需要废弃Token。 - 怎样保持客户端长时间保持登录状态?
服务器端提供刷新Token的接口, 客户端负责按一定的逻辑刷新服务器Token。
三、PHP实现
-
引入依赖 composer require lcobucci/jwt 3.*
-
功能实现
- 签发token, 设置签发人、接收人、唯一标识、签发时间、立即生效、过期时间、用户id、用户username、签名。其中,用户id、用户username是特意存储在token中的信息,也可以增加一些其他信息,这样在解析的时候就可以直接获取到这些信息,不能是敏感数据
- 验证令牌验证这个Token是不是合法的,有没有过期等,并可以解析出subject和claim里面的数据
传递jwt token的方式为Authorization中的Bearer Token,如下
封装工具类如下
<?php
namespace App\Utils;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Parser;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\Plain;
use Lcobucci\JWT\Validation\Constraint\IdentifiedBy;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\ValidationData;
class JwtUtil
{
protected $issuer = "http://example.com";
protected $audience = "http://example.org";
protected $id = "4f1g23a12aa11";
protected static $key = "8swQsm1Xb0TA0Jw5ASPwClKVZPoTyS7GvhtaW0MxzKEihs1BNpcS2q3FYMJ11111";
public function getToken()
{
$time = time();
$config = self::getConfig();
assert($config instanceof Configuration);
$token = $config->builder()
->issuedBy($this->issuer)
->permittedFor($this->audience)
->identifiedBy($this->id, true)
->issuedAt($time)
->canOnlyBeUsedAfter($time + 1)
->expiresAt($time + 3600)
->withClaim('uid', 20)
->withClaim('username', "Make")
->getToken($config->signer(), $config->signingKey());
return $token->toString();
}
public function verifyToken_bak($token)
{
try {
$config = self::getConfig();
assert($config instanceof Configuration);
$token = $config->parser()->parse($token);
assert($token instanceof Plain);
$validate_jwt_id = new IdentifiedBy($this->id);
$validate_issued = new IssuedBy($this->issuer);
$validate_aud = new PermittedFor($this->audience);
$config->setValidationConstraints($validate_jwt_id, $validate_issued, $validate_aud);
$constraints = $config->validationConstraints();
if (!$config->validator()->validate($token, ...$constraints)) {
die("token invalid!");
}
} catch (\Exception $e) {
die("error:" . $e->getMessage());
}
$jwtInfo = $token->claims();
return $jwtInfo->get("uid");
}
public static function getConfig()
{
$configuration = Configuration::forSymmetricSigner(
new Sha256(),
InMemory::base64Encoded(self::$key)
);
return $configuration;
}
public function verifyToken($token)
{
$token = (new Parser())->parse((string)$token);
$data = new ValidationData();
$data->setIssuer($this->issuer);
$data->setAudience($this->audience);
$data->setId($this->id);
if (!$token->validate($data)) {
die("token invalid!");
}
$jwtInfo = $token->claims();
return $jwtInfo->get("uid");
}
}
|