0%

JWT Token原理解析

一、JWT Token构成

头部Header . 载荷Payload . 签名Signature

1、头部 Header
声明了签名算法,如:['typ'=> 'JWT', 'alg' => 'HS256']
将以上数据定义成数组,再通过json_encode序列化,base64编码后得到头部信息。

2、载荷 Payload
即装载的数据,官方定义但不强制使用的数据字段:

1
2
3
4
5
6
7
iss (issuer):签发人
sub (subject):主题
aud (audience):受众
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
iat (Issued At):签发时间
jti (JWT ID):编号

将以上数据定义成数组,再通过json_encode序列化,base64编码后得到载荷信息。

注意:
sub:为JWT所面向的用户,userid,是否使用是可选的;
jti:为 jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

3、签名 Signature
使用HMAC对头部+点+载荷生成信息摘要,即签名

二、JWT Token优缺点

优点:

  • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用
  • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息
  • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的
  • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

缺点(安全相关):

  • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分
  • 保护好secret私钥,该私钥非常重要
  • 如果可以,请使用https协议

三、基于tymon/jwt-auth包的源码实现

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<?php
//laravel
//tymon/jwt-auth包 原理解析

//JWT头部
//签名算法支持:HS256, HS384, HS512
$header = ['typ'=> 'JWT', 'alg' => 'HS256']; //{"typ":"JWT","alg":"HS256"}
$header = base64UrlEncode(json_encode($header)); //序列化并Base64编码
//json_last_error() != JSON_ERROR_NONE 可判断json_encode是否执行成功

//JWT载荷
/*
iss (issuer):签发人
iat (Issued At):签发时间
exp (expiration time):过期时间
nbf (Not Before):生效时间,在此之前是无效的
jti (JWT ID):编号
sub (subject):主题
*/
$time = time();
$userId = '7525571047194611'; //用户雪花ID
$claims = [
'iss' => 'ipcloud-api',
'iat' => $time,
'exp' => intval($time) + 604800, //7天=7*24*60*60
'nbf' => $time,
'jti' => sha1($userId.rand(100000, 999999)),
'sub' => $userId,
]; //{"iss":"ipcloud-api","iat":1591068997,"exp":1591673797,"nbf":1591068997,"jti":"ac0d074c18c97ecbadfaaf0b6003ddc27a80356a","sub":"7525571047194611"};
$payload = base64UrlEncode(json_encode($claims)); //序列化并Base64编码

//签名密钥
$key = '7b21ff514564ea1be967ff7433485b00';

//JWT签名
//用header+payload创建签名:使用HMAC生成信息摘要
$signature = hash_hmac('sha256', implode('.', [$header, $payload]), $key, true); //最后一个参数设置成true,则返回二进制数据,否则返回16进制小写字符串格式表示的信息摘要
$signature = base64UrlEncode($signature);

//输出JWT Token
$token = implode('.', [$header, $payload, $signature]);
echo $token;

/**
* Encodes to base64url
* 一般而言接口提供方,都会要求对加密串进行base64urlencode,防止签名串被特殊字符分割,导致验证无法通过。
* 因为标准的Base64编码会出现+、/和=,所以不适合把Base64编码后的字符串放到URL中。一种针对URL的Base64编码可以在URL中使用的Base64编码,它仅仅是把+变成-,/变成_
* @param string $data
* @return string
*/
function base64UrlEncode($data)
{
return str_replace('=', '', strtr(base64_encode($data), '+/', '-_'));
}

Base64编码,是一种编码算法,不是加密算法,主要是为了将二进制数据转换成文本格式。
Base64编码的缺点是传输效率会降低,因为它把原始数据的长度增加了1/3。
因为标准的Base64编码会出现+、/和=,所以不适合把Base64编码后的字符串放到URL中。一种针对URL的Base64编码可以在URL中使用的Base64编码,它仅仅是把+变成-,/变成_
参考:https://www.liaoxuefeng.com/wiki/1252599548343744/1304227703947297
http://www.ruanyifeng.com/blog/2008/06/base64.html Base64编码原理

哈希算法又称摘要算法,是一种加密算法,主要是为了解决数据被篡改的问题。
使用哈希口令时,还要注意防止彩虹表攻击。

什么是彩虹表呢?如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。
然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表:

让彩虹表失效的做法就是:加盐(salt)

四、hash_hmac算法

Hmac算法是一种标准的基于密钥的哈希算法,可以配合MD5、SHA-1等哈希算法,计算的摘要长度和原摘要算法长度相同。
在php中,hash_hmac函数能将HMAC和一部分哈希加密算法结合起来实现HMAC-SHA1、HMAC-SHA256、HMAC-MD5等算法。函数介绍如下:

1
2
3
4
5
6
string hash_hmac(string $algo, string $data, string $key, bool $raw_output = false)
algo:要使用的哈希算法名称,可以是上述提到的md5,sha1等
data:要进行哈希运算的消息,也就是需要加密的明文。
key:使用HMAC生成信息摘要是所使用的密钥。
raw_output:该参数为可选参数,默认为false,如果设为true,则返回原始二进制数据表示的信息摘要,否则返回16进制小写字符串格式表示的信息摘要(注意是16进制数,而非简单的字母加数字)。
另外:如果algo参数指定的不是受支持的算法,将返回false。

五、tymon/jwt-auth包关键文件或代码摘要

vendor\lcobucci\jwt\src\Signer\Hmac\Sha256.php //$signer签名者
vendor\lcobucci\jwt\src\Builder.php //Token构建
vendor\lcobucci\jwt\src\Signer\BaseSigner.php
vendor\lcobucci\jwt\src\Token.php
vendor\tymon\jwt-auth\src\Providers\JWT\Lcobucci.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Create a JSON Web Token.
*
* @param array $payload
*
* @throws \Tymon\JWTAuth\Exceptions\JWTException
*
* @return string
*/
public function encode(array $payload)
{
// Remove the signature on the builder instance first.
$this->builder->unsign();

try {
foreach ($payload as $key => $value) {
$this->builder->set($key, $value);
}
$this->builder->sign($this->signer, $this->getSigningKey());
} catch (Exception $e) {
throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e);
}

return (string) $this->builder->getToken();
}

/**
* Decode a JSON Web Token.
*
* @param string $token
*
* @throws \Tymon\JWTAuth\Exceptions\JWTException
*
* @return array
*/
public function decode($token)
{
try {
$jwt = $this->parser->parse($token);
} catch (Exception $e) {
throw new TokenInvalidException('Could not decode token: '.$e->getMessage(), $e->getCode(), $e);
}

if (! $jwt->verify($this->signer, $this->getVerificationKey())) {
throw new TokenInvalidException('Token Signature could not be verified.');
}

return (new Collection($jwt->getClaims()))->map(function ($claim) {
return is_object($claim) ? $claim->getValue() : $claim;
})->toArray();
}

vendor\lcobucci\jwt\src\Builder.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Returns the resultant token
*
* @return Token
*/
public function getToken(Signer $signer = null, Key $key = null)
{
$signer = $signer ?: $this->signer;
$key = $key ?: $this->key;

if ($signer instanceof Signer) {
$signer->modifyHeader($this->headers);
}

$payload = [
$this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->headers)),
$this->encoder->base64UrlEncode($this->encoder->jsonEncode($this->claims))
];

$signature = $this->createSignature($payload, $signer, $key);

if ($signature !== null) {
$payload[] = $this->encoder->base64UrlEncode($signature);
}

return new Token($this->headers, $this->claims, $signature, $payload);
}

参考文章:

https://learnku.com/articles/10885/full-use-of-jwt
https://blog.csdn.net/mengzuchao/article/details/78473577
https://www.liaoxuefeng.com/wiki/1252599548343744/1255943717668160 加密与安全(廖雪峰)
http://www.ruanyifeng.com/blog/2008/06/base64.html Base64编码原理