0%

PHP实现JWT处理逻辑

JWT由三个部分组成:header.payload.signature
php

header部分:

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

对应base64UrlEncode编码为:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

说明:该字段为json格式。alg字段指定了生成signature的算法,默认值为 HS256,typ默认值为JWT

payload部分:

1
2
3
4
5
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}

对应base64UrlEncode编码为:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

说明:该字段为json格式,表明用户身份的数据,可以自己自定义字段,很灵活。sub 面向的用户,name 姓名 ,iat 签发时间。例如可自定义示例如下:

1
2
3
4
5
6
7
8
9
{

"iss": "admin", //该JWT的签发者
"iat": 1535967430, //签发时间
"exp": 1535974630, //过期时间
"nbf": 1535967430, //该时间之前不接收处理该Token
"sub": "www.admin.com", //面向的用户
"jti": "9f10e796726e332cec401c569969e13e" //该Token唯一标识
}

signature部分:

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

对应的签名为:keH6T3x1z7mmhKL1T3r9sQdAxxdzB6siemGMr_6ZOwU

最终得到的JWT的Token为(header.payload.signature):eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.keH6T3x1z7mmhKL1T3r9sQdAxxdzB6siemGMr_6ZOwU

说明:对header和payload进行base64UrlEncode编码后进行拼接。通过key(这里是123456)进行HS256算法签名。

JWT使用流程

  • 初次登录:用户初次登录,输入用户名密码
  • 密码验证:服务器从数据库取出用户名和密码进行验证
  • 生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT
  • 返还JWT:服务器的HTTP RESPONSE中将JWT返还
  • 带JWT的请求:以后客户端发起请求,HTTP REQUEST
  • HEADER中的Authorizatio字段都要有值,为JWT
  • 服务器验证JWT

相比token而言

相比token而言使用方式上和服务器的开销上有区别,原始的token在用户登录后(api同理)会给前端返回一个token同时服务器也需要存储这个token,前端在请求业务接口时会携带这个token,而服务器需要每次进行对token进行校验、数据库查询,检验是否合法;
而JWT的方式与token有一定区别,最大的区别在于前端每次请求后端携带的token服务端是不需要存储的,合法校验这一步换成通过JWT去校验,从而减少服务端的开销与资源浪费

JWT-php依赖的引入

1
composer require firebase/php-jwt

方法封装

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
55
56
57
58
59
60
61
62
63
64
65
66
67
<?php

namespace app\common\service;

use DomainException;
use Firebase\JWT\ExpiredException;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Firebase\JWT\SignatureInvalidException;
use InvalidArgumentException;
use UnexpectedValueException;

class JWTService
{
// 服务端随机key
const PRIVATE_KEY = "KOWl3yJFuB5K3QG9Vae6wCiCgenirgWC";

/**
* - jwt生成token
* @param $payload // payload
* @param $expire_time // 过期时间戳
* @param $key // 自身秘钥
* @param $alg // 算法签名
* @return string
*/
public function jwt_encode(
array $payload,
int $expire_time = 0,
string $key = self::PRIVATE_KEY,
string $alg = "HS256"
): string
{
if ($expire_time > 0) {
$payload['exp'] = $expire_time;
}
return JWT::encode($payload, $key, $alg);
}

/**
* - JWT 解析token
* @param $token // token
* @param $key // 自身秘钥
* @param $alg // 算法签名
* @return array
*/
public function jwt_decode($token, string $key = self::PRIVATE_KEY, string $alg = "HS256"): array
{
try {
$decoded_arr = JWT::decode($token, new Key($key, $alg));
return [true, "处理成功", $decoded_arr];
// 参数无效异常
} catch (InvalidArgumentException $e) {
return [false, "参数错误:".$e->getMessage()];
// 算法不支持or提供的秘钥无效 等异常
} catch (DomainException $e) {
return [false, "算法错误:".$e->getMessage()];
// 签名异常
} catch (SignatureInvalidException $e) {
return [false, "签名错误:".$e->getMessage()];
// 过期异常捕获
} catch (ExpiredException $e) {
return [false, "签名已过期:".$e->getMessage()];
} catch (UnexpectedValueException $e) {
return [false, "异常:".$e->getMessage()];
}
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
$payload = [
'user_name' => "xiexiang",
'password' => "123456",
'sex' => 1,
'age' => 2,
'exp' => 1692063675 // 设置过期时间戳
];

$token = (new JWTService())->jwt_encode($payload);
[$status, $msg, $data] = (new JWTService())->jwt_decode($token);
var_dump($status, $msg, (array)$data);die;