PHP JWT令牌怎么用

wen PHP项目 4

PHP JWT令牌实战指南:从入门到安全部署(附完整代码示例)


目录导读

  1. 什么是JWT?为什么PHP项目需要它?
  2. JWT的三大结构:Header、Payload、Signature深度拆解
  3. 环境准备:PHP 7.4+与Composer依赖安装
  4. PHP生成JWT令牌:手写HMAC签名与RS256非对称加密
  5. PHP验证JWT令牌:过期时间、签名校验与异常处理
  6. 完整实战:登录接口签发Token,API中间件鉴权
  7. 高频问答:过期刷新、注销黑名单、跨域CORS等10个坑
  8. 安全底线:防CSRF、密钥管理、算法混淆攻击

什么是JWT?为什么PHP项目需要它?
JWT(JSON Web Token)是一种轻量级的开放标准(RFC 7519),它通过加密签名在客户端与服务器之间安全传输JSON对象,在PHP开发中,JWT主要用于无状态身份认证——服务器无需存储Session,客户端每次请求携带Token即可证明身份,相比传统Session跨域困难、扩展性差的问题,JWT天然支持分布式架构和移动端,因此成为RESTful API安全的首选方案。

PHP JWT令牌怎么用

JWT的三大结构:Header、Payload、Signature深度拆解
一个标准JWT由三部分组成,用点号()分隔:

xxxxxxxxx.yyyyyyyyy.zzzzzzzzz
  • Header(头部):JSON对象,声明类型(typ: "JWT")和签名算法(alg: "HS256"RS256),PHP中通过base64UrlEncode(json_encode($header))生成。
  • Payload(负载):存放实际数据,包括官方字段(如iss发行者、exp过期时间、sub用户ID)和自定义字段(如user_role)。注意:Payload仅Base64编码,未加密,严禁放敏感信息如密码。
  • Signature(签名):将Header和Payload拼接后,用密钥(HS256对称加密)或私钥(RS256非对称加密)进行哈希运算,防止数据被篡改。这是JWT安全的核心。

环境准备:PHP 7.4+与Composer依赖安装
推荐使用firebase/php-jwt库(GitHub星标超5k),安装命令:

composer require firebase/php-jwt

若需RS256算法,另需安装openssl扩展(PHP默认开启),手动实现亦可,但生产环境务必用成熟库,避免时间戳函数time()$_SERVER['REQUEST_TIME']的时区问题。

PHP生成JWT令牌:手写HMAC签名与RS256非对称加密

案例1:HS256(对称加密,适用于单一服务端)

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
$key = 'your-secret-key-至少32位随机字符串'; // 存于.env,勿硬编码
$payload = [
    'iss' => 'https://api.example.com',  // 发行者
    'aud' => 'https://frontend.com',     // 受众
    'iat' => time(),                     // 签发时间
    'exp' => time() + 3600,              // 1小时后过期
    'uid' => 1024,                       // 用户ID
    'role' => 'admin'
];
$jwt = JWT::encode($payload, $key, 'HS256');
echo $jwt; // 输出如 eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...

案例2:RS256(非对称加密,适合微服务多端验证)

$privateKey = file_get_contents('/path/to/private.pem');
$jwt = JWT::encode($payload, $privateKey, 'RS256');
// 验证端使用公钥
$publicKey = file_get_contents('/path/to/public.pem');
$decoded = JWT::decode($jwt, new Key($publicKey, 'RS256'));

PHP验证JWT令牌:过期时间、签名校验与异常处理
验证必须包含三步操作:

  1. 捕获异常ExpiredException(过期)、SignatureInvalidException(签名错误)、BeforeValidException(未到生效时间)。
  2. 严格校验audiss,防止Token在其他系统被滥用。
  3. 使用JWT::decode()后立即转换为数组
try {
    $decoded = JWT::decode($jwt, new Key($secret, 'HS256'));
    $data = (array) $decoded; // 转为关联数组
} catch (\Exception $e) {
    http_response_code(401);
    echo json_encode(['error' => $e->getMessage()]);
    exit;
}

完整实战:登录接口签发Token,API中间件鉴权

登录接口(login.php)

// 验证用户名密码(此处省略查询过程)
if ($user && password_verify($pass, $user['password'])) {
    $payload = [
        'sub' => $user['id'],
        'exp' => time() + 7200,
        'scope' => 'read write'
    ];
    echo json_encode(['token' => JWT::encode($payload, $secret, 'HS256')]);
}

API中间件(auth_middleware.php)

$headers = apache_request_headers();
if (!isset($headers['Authorization'])) {
    http_response_code(401);
    exit('Missing token');
}
$token = str_replace('Bearer ', '', $headers['Authorization']);
try {
    $data = JWT::decode($token, new Key($secret, 'HS256'));
    $GLOBALS['user_id'] = $data->sub; // 存储到全局变量供后续业务使用
} catch (Exception $e) {
    http_response_code(403);
    exit('Invalid token');
}

高频问答:过期刷新、注销黑名单、跨域CORS等10个坑

  1. 问:Token过期后如何实现“无感刷新”?
    答:签发双Token——短期Access Token(如30分钟)+长期Refresh Token(存数据库),Access Token过期时,用Refresh Token调用/refresh接口换取新Token。Refresh Token需存服务器并设置失效逻辑。

  2. 问:用户退出登录,JWT还能用吗?
    答:JWT本身无法主动失效,常见方案是维护黑名单(Redis Key-Value),将退出用户的jti(JWT ID)存入并设置过期时间,中间件检查黑名单。

  3. 问:跨域请求时,Token放请求头还是Cookie?
    答:SPA(前后端分离)推荐放Authorization Header,避免CSRF攻击,若浏览器插件无法自定义Header,可放Cookie并设置SameSite=None; Secure(需HTTPS)。

  4. 问:HS256密钥泄露了怎么办?
    答:立即在.env中更换密钥,并通知所有用户重新登录,更安全方案是使用RS256,泄露私钥概率低,且公钥可公开。

  5. 问:JWT::decode()后为什么返回对象而非数组?
    答:库的返回值是stdClass对象,如果想直接使用数组,用(array) $decoded转换,但嵌套对象需递归转换。

  6. 问:Payload中可以放用户密码MD5吗?
    答:绝对严禁! 即使Base64编码,攻击者可轻易解码查看,可放用户ID、角色、权限标识,不敏感信息。

  7. 问:如何防止Token被重放攻击?
    答:在Payload中加入jti(唯一ID),服务端存储已用ID,配合iat时间戳限制,对金融类API建议配合Nonce一次性值。

  8. 问:Nginx/Apache服务器如何获取Authorization Header?
    答:Apache用getallheaders(),Nginx默认隐藏该头,需在配置中加入underscores_in_headers on;或使用$_SERVER['HTTP_AUTHORIZATION']

  9. 问:JWT长度会膨胀,影响请求性能吗?
    答:单Token通常2KB内,远小于HTTP包体限制,但如果存放大量自定义字段会显著增加体积,建议只存sub和必要的scope

  10. 问:PHP-FPM中JWT解密性能如何优化?
    答:HS256算法耗CPU极低(微秒级),RS256较重,可用APCu缓存公钥,减少file_get_contents I/O操作。

安全底线:防CSRF、密钥管理、算法混淆攻击

  • 密钥管理:密钥写在.env文件中,且永远不要提交到Git仓库,定期轮换(如每90天)。
  • 算法混淆攻击:攻击者可能篡改Header的algnone,务必在JWT::decode指定允许的算法,例如new Key($secret, 'HS256'),禁止动态获取alg字段。
  • 防CSRF:若使用Cookie存储Token,必须校验OriginReferer头,并设置SameSite=Lax
  • 敏感操作二次验证:修改密码、支付等关键操作,即使Token有效也应要求输入密码或短信验证码。

PHP搭配JWT并非简单调用两个函数,而是需要从生成、验证到安全策略的全链路设计,掌握上述核心思想后,建议阅读firebase/php-jwt源码中JWT::encodeJWT::decode的实现,你会发现时间戳处理、Base64填充符的细节处理正是生产级代码的严谨所在,从今天起,用JWT替换你的Session认证,拥抱无状态API时代。

抱歉,评论功能暂时关闭!