官网:https://jwt.io/
官网有案例测试。
jwt-framework
github:https://github.com/web-token/jwt-framework
文档:https://web-token.spomky-labs.com/
composer require web-token/jwt-framework
为什么要使用JWT
JWT是json web token缩写。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。基于token的身份验证可以替代传统的cookie+session身份验证方法。
它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:
简洁(Compact):可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快
自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
JWT由三个部分组成:header.payload.signature
1、header部分:
{
"typ": "JWT",
"alg": "HS256"
}
对应base64UrlEncode编码为:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
说明:该字段为json格式。alg字段指定了生成signature的算法,默认值为HS256,typ默认值为JWT
2、payload部分:
{
"iss": "xxxx",//该JWT的签发者
"iat": 1569746556,//签发时间
"exp": 1601282556,//过期时间
"nbf": 1569746556,//该时间之前不接收处理该Token
"jti": "NSiLlGKANgTRA9WD",//该Token唯一标识
"sub": xxxxxxx,//面向的用户,比如用户id
"prv": "f6b71549db8c2c42b75827aa44f02b7ee529d24d"
}
对应base64UrlEncode编码为:eyJpc3MiOiJodHRwOi8vMTcyLjMxLjIwNS43MC91Yy91Y2VudGVyL3JlQmluZGluZyIsImlhdCI6MTU2OTc0NjU1NiwiZXhwIjoxNjAxMjgyNTU2LCJuYmYiOjE1Njk3NDY1NTYsImp0aSI6Ik5TaUxsR0tBTmdUUkE5V0QiLCJzdWIiOjkzMjYwOSwicHJ2IjoiZjZiNzE1NDlkYjhjMmM0MmI3NTgyN2FhNDRmMDJiN2VlNTI5ZDI0ZCJ9
说明:该字段为json格式,表明用户身份的数据,可以自己自定义字段,很灵活。
3、signature部分:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
123456
)
说明:对header和payload进行base64UrlEncode编码后进行拼接。通过key(这里是123456)进行HS256算法签名。
对应的签名为:wwTa9XRLydYmrjCOgiB4Wq1_rzGZ4sVeLg3NZVr0SaA
4、最终得到的结果就是三段的拼接:header.payload.signature,即
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vMTcyLjMxLjIwNS43MC91Yy91Y2VudGVyL3JlQmluZGluZyIsImlhdCI6MTU2OTc0NjU1NiwiZXhwIjoxNjAxMjgyNTU2LCJuYmYiOjE1Njk3NDY1NTYsImp0aSI6Ik5TaUxsR0tBTmdUUkE5V0QiLCJzdWIiOjkzMjYwOSwicHJ2IjoiZjZiNzE1NDlkYjhjMmM0MmI3NTgyN2FhNDRmMDJiN2VlNTI5ZDI0ZCJ9.wwTa9XRLydYmrjCOgiB4Wq1_rzGZ4sVeLg3NZVr0SaA
5、使用
一般将生成的jwt串放在请求头的 Authorization 中传递,服务端来验证。
在使用 JWT 时需要注意以下事项:
代码:
'HS256', //生成signature的算法
'typ' => 'JWT' //类型
];
// payload
private $payload;
// 过期时间
private $ttl = 10;// 10s
private $refreshTtl = 7200;
//使用HMAC生成信息摘要时所使用的密钥
private $key = '123456';
// 是否开启黑名单,开启后在校验token的时候会检查黑名单,影响性能,但是安全。
private $openBlackList = false;
public function __construct()
{
}
// 设置载荷
private function setPayload($pay = [])
{
$this->payload = [
"iss" => "jwt_admin",//该JWT的签发者
"iat" => time(),//签发时间
"exp" => time() + $this->ttl,//过期时间
"nbf" => time(),//该时间之前不接收处理该Token
"jti" => md5(uniqid('JWT') . time()),//该Token唯一标识
"sub" => '',//面向的用户,比如用户id
];
$this->payload = array_merge($this->payload, $pay);
}
// 设置过期时间
public function setTtl($seconds)
{
$seconds = intval($seconds);
if ($seconds > 0) {
$this->ttl = $seconds;
}
}
// 生成token
public function getToken($payload)
{
$this->setPayload($payload);
if (is_array($this->payload)) {
$base64header = $this->base64UrlEncode(json_encode($this->header, JSON_UNESCAPED_UNICODE));
$base64payload = $this->base64UrlEncode(json_encode($this->payload, JSON_UNESCAPED_UNICODE));
$signature = $this->signature($base64header . '.' . $base64payload, $this->key, $this->header['alg']);
$token = $base64header . '.' . $base64payload . '.' . $signature;
return $token;
} else {
return false;
}
}
// 验证token
public function verifyToken($Token)
{
$tokens = explode('.', $Token);
if (count($tokens) != 3)
return false;
list($base64header, $base64payload, $sign) = $tokens;
//获取jwt算法
$base64decodeheader = json_decode($this->base64UrlDecode($base64header), JSON_OBJECT_AS_ARRAY);
if (empty($base64decodeheader['alg']))
return false;
//签名验证
if ($this->signature($base64header . '.' . $base64payload, $this->key, $base64decodeheader['alg']) !== $sign)
return false;
$payload = json_decode($this->base64UrlDecode($base64payload), JSON_OBJECT_AS_ARRAY);
// 判断黑名单 $payload['jti']
if($this->openBlackList){
}
//签发时间大于当前服务器时间验证失败
if (isset($payload['iat']) && $payload['iat'] > time())
return false;
//过期时间小宇当前服务器时间验证失败
if (isset($payload['exp']) && $payload['exp'] < time())
return false;
//该nbf时间之前不接收处理该Token
if (isset($payload['nbf']) && $payload['nbf'] > time())
return false;
return $payload;
}
// 刷新token延长过期时间
public function refreshToken($token){
}
/**
* 当token泄露,或者注销登录,将token加入黑名单,使用redis存储,key的过期时间应大于token的过期时间。
* 一般将payload中的jti作为key,因为jti是唯一标识,value可以设置空,因为并不需要。
*/
public function addBlackList(){
if($this->openBlackList){
}
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode编码实现
* 将 +/ 替换成 -_
* 将 = 删除
* @param string $input 需要编码的字符串
* @return string
*/
private function base64UrlEncode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
/**
* base64UrlEncode https://jwt.io/ 中base64UrlEncode解码实现
* @param string $input 需要解码的字符串
* @return bool|string
*/
private function base64UrlDecode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$addlen = 4 - $remainder;
$input .= str_repeat('=', $addlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
/**
* HMACSHA256签名 https://jwt.io/ 中HMACSHA256签名实现
* @param string $input 为base64UrlEncode(header).".".base64UrlEncode(payload)
* @param string $key
* @param string $alg 算法方式
* @return mixed
*/
private function signature($input, $key, $alg = 'HS256')
{
$alg_config = array(
'HS256' => 'sha256'
);
return $this->base64UrlEncode(hash_hmac($alg_config[$alg], $input, $key, true));
}
}
// 测试加密
$payload = array('sub' => '1234567890', 'name' => 'John Doe');
$jwt = new Jwt;
$token = $jwt->getToken($payload);
echo $token.PHP_EOL;
//对token进行验证签名
$getPayload = $jwt->verifyToken($token);
var_dump($getPayload);
echo PHP_EOL;