一、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
$header = ['typ'=> 'JWT', 'alg' => 'HS256']; $header = base64UrlEncode(json_encode($header));
$time = time(); $userId = '7525571047194611'; $claims = [ 'iss' => 'ipcloud-api', 'iat' => $time, 'exp' => intval($time) + 604800, 'nbf' => $time, 'jti' => sha1($userId.rand(100000, 999999)), 'sub' => $userId, ]; $payload = base64UrlEncode(json_encode($claims));
$key = '7b21ff514564ea1be967ff7433485b00';
$signature = hash_hmac('sha256', implode('.', [$header, $payload]), $key, true); $signature = base64UrlEncode($signature);
$token = implode('.', [$header, $payload, $signature]); echo $token;
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
|
public function encode(array $payload) { $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(); }
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
|
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编码原理