JWT即JSON Web Token,是目前前后端分离场景下主流的身份鉴权方案,通过加密签名生成的token可以在服务端无状态的情况下完成用户身份验证,相比传统的session方案更适合分布式系统部署。

JWT的基本结构
一个标准的JWT字符串由三部分组成,分别是头部Header、载荷Payload、签名Signature,三部分之间用点号分隔。Header主要存放签名算法和token类型,Payload存放需要传递的用户信息和过期时间等数据,Signature是对前两部分的加密签名,用于校验token是否被篡改。
PHP生成JWT token的实现
生成JWT的核心步骤是先构造Header和Payload的JSON字符串,然后进行Base64Url编码,再使用指定的签名算法对前两部分拼接后的内容进行签名,最后拼接成完整的token字符串。下面是具体的实现代码:
<?php
class JwtService {
// 签名密钥,实际生产环境需要妥善保管,不要硬编码在代码中
private $secret = 'your_jwt_secret_key_2024';
// 签名算法,这里使用HS256
private $alg = 'HS256';
/**
* 生成JWT token
* @param array $payload 载荷数据,需要包含用户标识等信息
* @param int $expire 过期时间,单位秒,默认3600秒即1小时
* @return string 生成的JWT token
*/
public function generateToken($payload, $expire = 3600) {
// 构造Header
$header = [
'alg' => $this->alg,
'typ' => 'JWT'
];
// 给Payload添加过期时间和签发时间
$payload['iat'] = time();
$payload['exp'] = time() + $expire;
// 对Header和Payload进行Base64Url编码
$base64Header = $this->base64UrlEncode(json_encode($header, JSON_UNESCAPED_UNICODE));
$base64Payload = $this->base64UrlEncode(json_encode($payload, JSON_UNESCAPED_UNICODE));
// 生成签名
$signature = $this->generateSignature($base64Header, $base64Payload);
// 拼接成完整的JWT token
return $base64Header . '.' . $base64Payload . '.' . $signature;
}
/**
* Base64Url编码,替换标准Base64中的+和/,去掉末尾的=
* @param string $data 需要编码的数据
* @return string 编码后的字符串
*/
private function base64UrlEncode($data) {
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}
/**
* 生成签名
* @param string $base64Header 编码后的Header
* @param string $base64Payload 编码后的Payload
* @return string 签名字符串
*/
private function generateSignature($base64Header, $base64Payload) {
$content = $base64Header . '.' . $base64Payload;
if ($this->alg == 'HS256') {
$signature = hash_hmac('sha256', $content, $this->secret, true);
} else {
// 可以扩展其他签名算法
throw new Exception('不支持的签名算法');
}
return $this->base64UrlEncode($signature);
}
}
// 使用示例
$jwtService = new JwtService();
// 载荷中存放用户ID和用户名
$payload = [
'user_id' => 1001,
'username' => 'test_user'
];
// 生成1小时过期的token
$token = $jwtService->generateToken($payload, 3600);
echo '生成的token:' . $token;
?>
JWT token的校验与过期处理
服务端接收到客户端传递的token后,需要先拆分token的三部分,然后校验签名是否正确,再检查过期时间是否过期。如果签名错误说明token被篡改,直接拒绝请求;如果已经过期则需要根据刷新机制处理。
下面是token校验的实现代码:
<?php
class JwtService {
// 之前的属性和方法省略,这里补充校验相关方法
private $secret = 'your_jwt_secret_key_2024';
private $alg = 'HS256';
// 之前的base64UrlEncode、generateSignature方法省略
/**
* 校验JWT token
* @param string $token 需要校验的token
* @return array 校验结果,包含status和data字段
*/
public function verifyToken($token) {
// 拆分token
$parts = explode('.', $token);
if (count($parts) != 3) {
return ['status' => false, 'msg' => 'token格式错误'];
}
list($base64Header, $base64Payload, $signature) = $parts;
// 校验签名
$expectedSignature = $this->generateSignature($base64Header, $base64Payload);
if ($signature != $expectedSignature) {
return ['status' => false, 'msg' => 'token签名错误,可能被篡改'];
}
// 解析Payload
$payload = json_decode(base64_decode(str_replace(['-', '_'], ['+', '/'], $base64Payload)), true);
if (!$payload) {
return ['status' => false, 'msg' => 'token载荷解析失败'];
}
// 检查是否过期
if (isset($payload['exp']) && $payload['exp'] < time()) {
return ['status' => false, 'msg' => 'token已过期', 'code' => 401001];
}
return ['status' => true, 'data' => $payload];
}
}
// 使用示例
$jwtService = new JwtService();
// 假设从请求头中获取到的token
$clientToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMDAxLCJ1c2VybmFtZSI6InRlc3RfdXNlciIsImlhdCI6MTY5MDAwMDAwMCwiZXhwIjoxNjkwMDAzNjAwfQ.abc123';
$result = $jwtService->verifyToken($clientToken);
if ($result['status']) {
echo 'token校验通过,用户信息:' . json_encode($result['data']);
} else {
echo 'token校验失败:' . $result['msg'];
}
?>
token刷新机制的实现
为了避免用户频繁登录,通常会设置两个过期时间:access_token的短期过期时间(比如1小时)和refresh_token的长期过期时间(比如7天)。当access_token过期时,客户端携带refresh_token请求刷新接口,服务端校验refresh_token有效后,生成新的access_token返回给客户端。
实现刷新机制需要注意几个要点:refresh_token需要单独存储,比如存放在服务端的缓存或者数据库中,避免被盗用;refresh_token过期后需要用户重新登录;刷新token的操作需要校验客户端身份,避免恶意刷新。
下面是刷新token的示例代码:
<?php
class JwtService {
// 之前的属性和方法省略
private $secret = 'your_jwt_secret_key_2024';
private $alg = 'HS256';
// 假设这里有一个存储refresh_token的数组,实际生产环境用Redis或数据库存储
private $refreshTokenStore = [];
/**
* 生成refresh_token并存储
* @param int $userId 用户ID
* @param int $expire refresh_token过期时间,默认7天
* @return string refresh_token
*/
public function generateRefreshToken($userId, $expire = 604800) {
$refreshToken = bin2hex(random_bytes(32));
$this->refreshTokenStore[$refreshToken] = [
'user_id' => $userId,
'exp' => time() + $expire
];
return $refreshToken;
}
/**
* 刷新access_token
* @param string $refreshToken 客户端传递的refresh_token
* @return array 刷新结果
*/
public function refreshAccessToken($refreshToken) {
// 校验refresh_token是否存在且未过期
if (!isset($this->refreshTokenStore[$refreshToken])) {
return ['status' => false, 'msg' => 'refresh_token不存在'];
}
$tokenInfo = $this->refreshTokenStore[$refreshToken];
if ($tokenInfo['exp'] < time()) {
unset($this->refreshTokenStore[$refreshToken]);
return ['status' => false, 'msg' => 'refresh_token已过期,请重新登录'];
}
// 生成新的access_token
$payload = [
'user_id' => $tokenInfo['user_id']
];
$newAccessToken = $this->generateToken($payload, 3600);
return ['status' => true, 'access_token' => $newAccessToken];
}
}
// 使用示例
$jwtService = new JwtService();
// 用户登录成功后生成access_token和refresh_token
$userId = 1001;
$accessToken = $jwtService->generateToken(['user_id' => $userId], 3600);
$refreshToken = $jwtService->generateRefreshToken($userId, 604800);
echo '登录成功,access_token:' . $accessToken . ',refresh_token:' . $refreshToken;
// 当access_token过期时,调用刷新接口
$refreshResult = $jwtService->refreshAccessToken($refreshToken);
if ($refreshResult['status']) {
echo '刷新成功,新的access_token:' . $refreshResult['access_token'];
} else {
echo '刷新失败:' . $refreshResult['msg'];
}
?>
注意事项
- JWT的密钥需要妥善保管,不要泄露给客户端,否则攻击者可以伪造token。
- 不要在JWT的Payload中存放敏感信息,因为Payload只是Base64编码,不是加密,任何人都可以解码查看内容。
- token的过期时间不要设置过长,access_token建议设置1小时以内,refresh_token建议设置7天以内,降低被盗用的风险。
- 如果需要让已经签发的token立即失效,比如用户修改密码或者退出登录,需要维护一个黑名单,校验token时先检查是否在黑名单中,不过这会增加服务端的存储成本,需要根据实际场景选择。
以上就是PHP实现JWT鉴权、token生成与刷新过期机制的完整方案,开发者可以根据自己的业务需求调整签名算法、过期时间和存储方式,搭建适合自己项目的鉴权体系。