Frenlee

JWT的应用

理解JWT

什么是JWT

Json Web token(JWT)是基于开放标准(RFC 7519)设计的一种紧凑且独立的认证机制.它比较安全,可验证.可以对该token进行加密验证,一般用于用户认证和信息的安全传输上.它比较小,可以传输在URL.POST,参数,http头文件中,它可以携带一些用户自定义的信息.所以它可以用来做接口方面的认证,单点登录的用户认证.

JWT的构成

JWT 主要由三个部分构成:

  • Header 头部
  • Payload 信息承载体
  • Signature 签名
    这三个部分有.号隔开,格式如下
    header.payload.signature

头部由两个部分组成,这部分在token上会经过base64编码,所以这部分在是可以被反编码看到明文的.

  • 声明类型,这里是JWT
  • 声明加密的算法,例如 HMAC,SHA256或者RSA.
1
2
3
4
{
"alg": "HMAC",
"typ": "JWT"
}

Payload

第二部分是信息承载体,主要由三个部分组成:

  • 保留的声明
  • 公共的声明
  • 私有的声明
保留的声明
  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

这部分是保留的声明,当然你也可以当做其他用法用,不过不建议这样做.不然别人获取到你token之后你还得大费口舌的去解释一番.

公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

定义一个payload:

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}

签名

第三部分签名,它主要是用来验证token的安全性,因为token一般是由生成者来使用的.或者生成者和使用者会约定一个加密的key,基于header和payload以及key来生成这个部分,然后验证的时候也是基于这些数据来验证的安全性.例如使用sha256算法:

1
2
3
4
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

在PHP中应用

jwt的流程如下:
jwt

加载语言组件代码

可以在(jwt.io)官方网站可以找到各种语言的JWT代码,下面我们已PHP为力,使用composer来加载下代码(https://github.com/lcobucci/jwt):

1
composer require lcobucci/jwt

使用

生成Token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require_once './vendor/autoload.php';

// basic usage
use \Lcobucci\JWT\Builder;
use \Lcobucci\JWT\Parser;
use \Lcobucci\JWT\Signer\Hmac\Sha256;

$signer = new Sha256(); // 签名算法
//iss(签发者) , exp(过期时间戳) , sub(面向的用户) , aud(接收方) , iat(签发时间)
$token = (new Builder())->setIssuer('http://example.com') // 签发者
->setAudience('http://example.org') // 接受方
->setId('123456', true) // jwt唯一标识,用作一次性的token
->setIssuedAt(time()) // 签发时间
->setNotBefore(time()+60) // 能使用的时间
->setExpiration(time() + 3600) // 过期时间
->set('uid', 1) // 自定义字段
->sign($signer, 'testing') // testing为设定的秘钥,这部分没有的话token第三部分会有空
->getToken();
echo (string)$token;

结果如下:

1
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImp0aSI6IjEyMzQ1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUub3JnIiwianRpIjoiMTIzNDU2IiwiaWF0IjoxNDk0OTMwODQ5LCJuYmYiOjE0OTQ5MzA5MDksImV4cCI6MTQ5NDkzNDQ0OSwidWlkIjoxfQ.Ui0lVBJ3X2AP_m_aoMgSn_ALCP_iWiSuSiIhRbWXEjs
验证Token

获取到Token只有对其进行解析,如果是加密的,需要先对其进行有效性和安全验证

1
2
3
4
5
6
7
8
9
10
11
12
13
$data = new \Lcobucci\JWT\ValidationData();
$data->setIssuer("http://example.com");
$data->setAudience("http://example.org");
$data->setId('123456');

var_dump($token->validate($data)); // false
$data->setCurrentTime(time()+3600);
var_dump($token->validate($data)); // true
$data->setCurrentTime(time()+4000);
var_dump($token->validate($data)); // false 数据有效性验证

var_dump($token->verify($signer, 'test1')); // false
var_dump($token->verify($signer, 'testing')); // true 签名验证
解析Token

当验证通过后,需要解析中间那一部分然后使用,其实自己解析的话就base64反编码一下就好了,所以这部分切记不可传递敏感信息

1
2
3
4
5
6
7
// 解析
$token = (new Parser())->parse((string)$token); // 解析一个token
$token->getHeaders();
$token->getClaims();
var_dump($token->getHeader('jti')); // 保留的声明字段
var_dump($token->getClaim('iss')); // 保留的声明字段
var_dump($token->getClaim('uid')); // 自定义的声明字段

参考