PHP JWT(JSON Web Token)实现与身份验证方法
在前后端分离的开发架构中,传统的Session会话机制存在跨域支持差、服务端存储压力大的问题,JWT(JSON Web Token)成为了常用的身份验证方案。JWT本质是一个经过签名的JSON数据字符串,它可以在客户端和服务端之间安全传递用户身份信息,无需服务端存储会话数据,非常适合分布式系统和API接口的身份校验场景。
JWT的基本结构
一个标准的JWT字符串由三部分组成,之间用点号(.)分隔:
- Header(头部):声明令牌的类型(通常是JWT)和使用的签名算法,比如HMAC SHA256或者RSA。
- Payload(载荷):存储实际需要传递的数据,比如用户ID、过期时间等,注意不要存放敏感信息。
- Signature(签名):使用Header中指定的算法,结合服务端持有的密钥,对前两部分的内容进行签名,用于验证令牌是否被篡改。
三部分组合后形成的JWT字符串示例:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxMjMsImV4cCI6MTY5ODc2NTQwMH0.xxxxxxx
PHP环境准备
PHP中使用JWT通常需要借助第三方库简化操作,最常用的是firebase/php-jwt库,我们可以通过Composer进行安装:
composer require firebase/php-jwt
如果项目没有使用Composer,也可以手动下载库的源码集成到项目中,不过建议使用Composer管理依赖,更方便后续更新和维护。
PHP实现JWT的生成与验证
下面我们通过完整的代码示例演示JWT的生成、校验的完整流程:
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
// 定义服务端密钥,生产环境需要妥善保管,不要泄露
$secretKey = 'your_strong_secret_key_here_123456';
// 定义使用的签名算法
$algorithm = 'HS256';
/**
* 生成JWT令牌
* @param int $userId 用户ID
* @param int $expireSeconds 令牌有效期(秒),默认1小时
* @return string 生成的JWT字符串
*/
function generateJwtToken($userId, $expireSeconds = 3600) {
global $secretKey, $algorithm;
$currentTime = time();
$payload = [
'iss' => 'ipipp.com', // 令牌签发方
'aud' => 'ipipp.com', // 令牌受众
'iat' => $currentTime, // 令牌签发时间
'exp' => $currentTime + $expireSeconds, // 令牌过期时间
'user_id' => $userId // 自定义载荷:用户ID
];
// 生成JWT
$jwt = JWT::encode($payload, $secretKey, $algorithm);
return $jwt;
}
/**
* 验证JWT令牌
* @param string $jwt 待验证的JWT字符串
* @return array 验证结果,包含状态和解析后的载荷
*/
function validateJwtToken($jwt) {
global $secretKey, $algorithm;
try {
// 解码并验证JWT,会自动校验签名和过期时间
$decoded = JWT::decode($jwt, new Key($secretKey, $algorithm));
return [
'status' => true,
'data' => (array)$decoded
];
} catch (Exception $e) {
// 捕获验证失败异常,比如签名错误、令牌过期等
return [
'status' => false,
'msg' => $e->getMessage()
];
}
}
// 示例1:生成令牌
$userId = 123;
$token = generateJwtToken($userId);
echo "生成的JWT令牌:{$token}" . PHP_EOL;
// 示例2:验证令牌
$validateResult = validateJwtToken($token);
if ($validateResult['status']) {
echo "令牌验证通过,用户ID:" . $validateResult['data']['user_id'] . PHP_EOL;
} else {
echo "令牌验证失败:" . $validateResult['msg'] . PHP_EOL;
}
// 示例3:验证过期令牌(模拟过期场景,实际使用中不需要主动修改令牌)
$expiredToken = generateJwtToken($userId, -1); // 设置有效期为-1秒,直接过期
$expiredResult = validateJwtToken($expiredToken);
if (!$expiredResult['status']) {
echo "过期令牌验证结果:" . $expiredResult['msg'] . PHP_EOL;
}上述代码中,我们首先引入了firebase/php-jwt库的相关类,然后定义了生成和验证JWT的两个函数。生成令牌时,我们自定义了载荷中的签发方、受众、过期时间等标准字段,同时添加了用户ID作为业务字段;验证令牌时,库会自动校验签名的有效性以及令牌是否过期,无需我们手动处理这些逻辑。
基于JWT的身份验证完整流程
在实际的项目身份验证场景中,JWT的使用流程通常如下:
- 用户通过登录接口提交账号密码,服务端校验账号密码的正确性。
- 校验通过后,调用生成JWT的函数,传入用户ID等信息生成令牌,返回给客户端。
- 客户端收到令牌后,将其存储在本地(比如浏览器的localStorage、Cookie,或者APP的本地存储中)。
- 后续客户端请求需要身份校验的接口时,在请求头中携带令牌,通常放在
Authorization头中,格式为Bearer {jwt_token}。 - 服务端收到请求后,从请求头中取出令牌,调用验证函数校验令牌的有效性。
- 如果令牌有效,从载荷中取出用户ID,查询用户信息完成后续业务逻辑;如果令牌无效或者过期,返回401未授权状态码,提示客户端重新登录。
注意事项
- JWT的载荷部分是Base64编码的,并不是加密的,所以不要存放密码、身份证号等敏感信息,避免信息泄露。
- 服务端的密钥必须严格保密,一旦密钥泄露,攻击者可以伪造任意JWT令牌,绕过身份验证。
- 合理设置令牌的有效期,有效期过长会增加令牌泄露后的风险,过短会导致用户频繁需要重新登录,通常设置为1-2小时比较合适,也可以配合刷新令牌机制延长登录状态。
- 如果要实现用户登出功能,由于JWT本身是无状态的,服务端没有存储令牌,所以登出时只能让客户端主动删除本地存储的令牌,无法实现服务端的强制失效,如果有强制失效的需求,需要额外维护一个令牌黑名单。