PHP `JWT` (JSON Web Tokens) 认证:无状态认证与安全实践

嘿,大家好!欢迎来到今天的“PHP JWT认证:无状态认证与安全实践”讲座。我是你们今天的导游,带大家一起探索一下JWT这个神秘又实用的家伙。准备好了吗? Let’s go!

一、 JWT是个啥? (What is JWT?)

想象一下,你去参加一个派对,门口的保安大叔需要验证你的身份。传统的做法是,每次你进出不同的房间,都要重新给保安大叔看你的身份证。这很麻烦,对吧?

JWT就像一张一次性签发的“通行证”,你只要拿上这张通行证,就可以在不同的房间(系统)里自由穿梭,而不用每次都给保安大叔重新验证身份。

更专业一点说,JWT (JSON Web Token) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输 JSON 对象作为数据。

二、 JWT的结构 (JWT Structure)

JWT主要由三个部分组成,用点号(.)分隔:

  1. Header (头部)
  2. Payload (载荷)
  3. Signature (签名)

让我们逐一击破:

  • Header (头部): 告诉我们这个Token是用什么算法加密的,通常包含两个部分:alg (算法) 和 typ (类型)。

    例如:

    {
      "alg": "HS256",
      "typ": "JWT"
    }
    • alg: 指定签名算法,比如 HS256 (HMAC SHA256),RS256 (RSA SHA256) 等。
    • typ: 指定token类型,这里固定为 JWT
  • Payload (载荷): 包含一些声明 (claims)。声明是一些关于实体(通常是用户)和其他数据的声明。载荷里可以放一些公开的信息。

    Payload 里有三种类型的声明:

    • Registered claims (注册声明): 是一些预定义的声明,不强制使用,但推荐使用,比如 iss (issuer,签发者),sub (subject,主题),aud (audience,受众),exp (expiration time,过期时间),nbf (not before,生效时间),iat (issued at,签发时间),jti (JWT ID,JWT唯一标识)。

    • Public claims (公共声明): 可以自定义,但是为了避免冲突,建议使用 IANA JSON Web Token Registry 或 URI。

    • Private claims (私有声明): 自定义声明,用于在应用程序之间共享信息。

    例如:

    {
      "iss": "example.com",
      "sub": "1234567890",
      "name": "John Doe",
      "admin": true,
      "iat": 1516239022
    }
  • Signature (签名): 用于验证消息的完整性,确保 JWT 没有被篡改。签名是使用 Header 中指定的算法,对 Header 和 Payload 进行加密生成的。

    签名计算公式如下:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret
    )
    • headerpayload 先进行 Base64 URL 编码。
    • 使用 Header 中指定的算法 (例如 HS256),加上一个密钥 (secret),对编码后的 Header 和 Payload 进行签名。

三、 PHP中使用JWT (JWT with PHP)

好了,理论知识铺垫完毕,接下来我们看看如何在PHP中使用JWT。

首先,你需要安装一个JWT库。推荐使用 firebase/php-jwt

composer require firebase/php-jwt

安装完成后,就可以开始使用了。

1. 生成JWT (Creating a JWT)

<?php

require_once 'vendor/autoload.php';

use FirebaseJWTJWT;
use FirebaseJWTKey;

$key = "example_key"; // 你的密钥,请务必保管好!
$payload = array(
    "iss" => "http://example.org",
    "aud" => "http://example.com",
    "iat" => time(),
    "nbf" => time() + 10, // 10秒后生效
    "exp" => time() + 3600, // 1小时后过期
    "data" => array(
        "userId" => 123,
        "userName" => "John Doe"
    )
);

/**
 * IMPORTANT:
 * You must specify supported algorithms for your application. See
 * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-40
 * for a list of spec-compliant algorithms.
 */
$jwt = JWT::encode($payload, $key, 'HS256');

echo $jwt;
?>

代码解释:

  • $key: 这是你的密钥,用于签名和验证JWT。请务必妥善保管,不要泄露!
  • $payload: 这是JWT的载荷,包含了一些声明信息。
  • JWT::encode(): 使用指定的算法 (HS256) 和密钥,对载荷进行签名,生成JWT。

2. 验证JWT (Verifying a JWT)

<?php

require_once 'vendor/autoload.php';

use FirebaseJWTJWT;
use FirebaseJWTKey;

$jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9leGFtcGxlLm9yZyIsImF1ZCI6Imh0dHA6XC9cL2V4YW1wbGUuY29tIiwiaWF0IjoxNjgzNDU5MzU0LCJuYmYiOjE2ODM0NTk0NjQsImV4cCI6MTY4MzQ2Mjk1NCwiZGF0YSI6eyJ1c2VySWQiOjEyMywidXNlck5hbWUiOiJKb2huIERvZSJ9fQ.dE09w0g7r98-65iXlF94b37g0-49t80r12n281m"; // 假设这是你收到的JWT
$key = "example_key"; // 你的密钥

try {
    $decoded = JWT::decode($jwt, new Key($key, 'HS256'));

    print_r($decoded);

    // 可以访问载荷中的数据
    echo "User ID: " . $decoded->data->userId;

} catch (Exception $e) {
    echo "Error: " . $e->getMessage();
}
?>

代码解释:

  • JWT::decode(): 使用密钥和算法,验证JWT的签名,并解码载荷。
  • try...catch: 捕获可能发生的异常,比如无效的签名、过期的Token等。

四、 JWT的优点 (Advantages of JWT)

  • 无状态 (Stateless): JWT自身包含了所有必要的信息,服务器不需要存储会话信息,减轻了服务器的压力。
  • 可扩展性 (Scalability): 由于无状态,可以很容易地扩展应用程序,而不用担心会话同步的问题。
  • 跨域认证 (Cross-Domain Authentication): JWT可以用于跨域认证,方便不同域名下的应用程序共享用户信息。
  • 安全性 (Security): JWT使用签名来保证数据的完整性,可以防止数据被篡改。

五、 JWT的安全实践 (JWT Security Practices)

虽然JWT很强大,但如果不注意安全问题,也会带来一些风险。

  1. 密钥安全 (Secret Key Security)

    • 不要在客户端存储密钥! 这是最重要的一点。客户端是不安全的,任何存储在客户端的信息都可能被窃取。
    • 使用强密码作为密钥。 不要使用容易被猜到的密码。
    • 定期更换密钥。 定期更换密钥可以降低密钥泄露的风险。
    • 使用环境变量或配置文件存储密钥。 不要将密钥硬编码到代码中。
  2. 算法选择 (Algorithm Selection)

    • 避免使用 alg: none 这是一个非常危险的设置,会导致JWT没有签名,任何人都可以伪造JWT。
    • 优先选择非对称加密算法 (例如 RS256)。 非对称加密算法使用公钥加密,私钥解密。公钥可以公开,私钥必须保密。这样可以防止密钥泄露后,攻击者伪造JWT。
    • 如果使用对称加密算法 (例如 HS256),请务必保护好密钥。
  3. 过期时间 (Expiration Time)

    • 设置合理的过期时间。 过期时间太长会增加Token被盗用的风险,过期时间太短会影响用户体验。
    • 实现Token刷新机制。 当Token即将过期时,可以自动刷新Token,而不需要用户重新登录。
  4. 防止重放攻击 (Replay Attacks)

    • 使用 jti (JWT ID) 声明。 jti 是JWT的唯一标识,可以用于防止重放攻击。服务器可以记录已经使用过的 jti,如果收到相同的 jti,则拒绝请求。
  5. Payload安全 (Payload Security)

    • 不要在Payload中存储敏感信息。 Payload是Base64编码的,虽然不能直接读取,但是可以很容易地解码。因此,不要在Payload中存储密码、银行卡号等敏感信息。
    • 对Payload进行加密。 可以使用 JWE (JSON Web Encryption) 对Payload进行加密。
  6. 验证所有声明 (Verify All Claims)

    • 验证 iss (issuer),aud (audience),exp (expiration time) 等声明。 确保JWT是由你信任的签发者签发的,并且是为你的应用程序设计的,并且没有过期。

六、 JWT与传统Session认证的对比 (JWT vs Session)

特性 JWT Session
存储位置 客户端 (通常存储在cookie或localStorage) 服务器端 (通常存储在数据库或内存)
状态 无状态 有状态
可扩展性 更好 较差
安全性 需要注意密钥安全和Payload安全 需要注意Session劫持和Session固定攻击
跨域认证 支持 较复杂,需要配置CORS或使用其他技术
服务器压力 较低 较高

表格总结:

维度 JWT Session 备注
存储 客户端 (浏览器/App) 服务端 (数据库/内存) JWT 存储在客户端,减少服务端存储压力;Session 存储在服务端,需要维护会话状态。
状态 无状态 有状态 JWT 无状态,服务端无需保存会话信息;Session 有状态,服务端需要保存会话信息。
扩展性 易于水平扩展 扩展性较差 JWT 无状态,易于在分布式系统中水平扩展;Session 需要考虑会话共享问题(如:Session 复制、共享存储等)。
安全 密钥安全至关重要,防重放攻击 防Session劫持、Session固定攻击 JWT 安全性依赖于密钥的安全性,需要合理设置过期时间、防止重放攻击;Session 需要防止Session劫持和Session固定攻击。
跨域 支持跨域 需要特殊处理 JWT 天然支持跨域,因为所有信息都在 Token 中;Session 需要配置 CORS 或使用其他跨域解决方案。
性能 服务端压力小 服务端压力大 JWT 服务端压力小,无需存储会话信息;Session 服务端压力大,需要维护大量的会话信息。
使用场景 API 认证、单点登录 (SSO) 传统的 Web 应用登录 JWT 适用于 API 认证和单点登录场景,可以方便地在不同服务之间共享用户信息;Session 适用于传统的 Web 应用登录场景,可以方便地管理用户的会话状态。

七、 总结 (Conclusion)

JWT是一个非常有用的认证方式,特别是在API认证和单点登录场景下。但是,在使用JWT时,一定要注意安全问题,确保你的应用程序是安全的。

记住,没有绝对的安全,只有相对的安全。我们要做的就是不断学习,不断提升自己的安全意识,才能更好地保护我们的应用程序。

好了,今天的讲座就到这里了。希望大家对JWT有了更深入的了解。如果有什么问题,欢迎提问!谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注