嘿,大家好!欢迎来到今天的“PHP JWT认证:无状态认证与安全实践”讲座。我是你们今天的导游,带大家一起探索一下JWT这个神秘又实用的家伙。准备好了吗? Let’s go!
一、 JWT是个啥? (What is JWT?)
想象一下,你去参加一个派对,门口的保安大叔需要验证你的身份。传统的做法是,每次你进出不同的房间,都要重新给保安大叔看你的身份证。这很麻烦,对吧?
JWT就像一张一次性签发的“通行证”,你只要拿上这张通行证,就可以在不同的房间(系统)里自由穿梭,而不用每次都给保安大叔重新验证身份。
更专业一点说,JWT (JSON Web Token) 是一个开放标准 (RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输 JSON 对象作为数据。
二、 JWT的结构 (JWT Structure)
JWT主要由三个部分组成,用点号(.
)分隔:
- Header (头部)
- Payload (载荷)
- 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 )
header
和payload
先进行 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很强大,但如果不注意安全问题,也会带来一些风险。
-
密钥安全 (Secret Key Security)
- 不要在客户端存储密钥! 这是最重要的一点。客户端是不安全的,任何存储在客户端的信息都可能被窃取。
- 使用强密码作为密钥。 不要使用容易被猜到的密码。
- 定期更换密钥。 定期更换密钥可以降低密钥泄露的风险。
- 使用环境变量或配置文件存储密钥。 不要将密钥硬编码到代码中。
-
算法选择 (Algorithm Selection)
- 避免使用
alg: none
! 这是一个非常危险的设置,会导致JWT没有签名,任何人都可以伪造JWT。 - 优先选择非对称加密算法 (例如 RS256)。 非对称加密算法使用公钥加密,私钥解密。公钥可以公开,私钥必须保密。这样可以防止密钥泄露后,攻击者伪造JWT。
- 如果使用对称加密算法 (例如 HS256),请务必保护好密钥。
- 避免使用
-
过期时间 (Expiration Time)
- 设置合理的过期时间。 过期时间太长会增加Token被盗用的风险,过期时间太短会影响用户体验。
- 实现Token刷新机制。 当Token即将过期时,可以自动刷新Token,而不需要用户重新登录。
-
防止重放攻击 (Replay Attacks)
- 使用
jti
(JWT ID) 声明。jti
是JWT的唯一标识,可以用于防止重放攻击。服务器可以记录已经使用过的jti
,如果收到相同的jti
,则拒绝请求。
- 使用
-
Payload安全 (Payload Security)
- 不要在Payload中存储敏感信息。 Payload是Base64编码的,虽然不能直接读取,但是可以很容易地解码。因此,不要在Payload中存储密码、银行卡号等敏感信息。
- 对Payload进行加密。 可以使用 JWE (JSON Web Encryption) 对Payload进行加密。
-
验证所有声明 (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有了更深入的了解。如果有什么问题,欢迎提问!谢谢大家!