php + JWT详解
后台-插件-广告管理-内容页头部广告(手机) |
JWT 概述
JWT(JSON Web Token)是一个开放的标准(RFC 7519),它定义了一种简洁、自包含的方式,用于通信双方之间安全地将信息作为一个 JSON 对象进行传输。此信息可以被验证和信任,因为它被数字签名。
JWT 结构主要由三部分组成:
- Header:头部通常由两部分组成:token 类型和使用的哈希算法。
- Payload:载荷包含声明。声明是关于实体(通常是用户)的信息以及附加元数据。
- Signature:生成签名部分需要对 header 和 payload 的 Base64Url 编码进行编码,然后使用 header 中指定的哈希算法和一个密钥进行签名。
JWT 主要有两个作用:身份验证和信息交换。
- 身份验证:最常见的使用场景是身份验证。当用户登录后,每个后续请求都会包含 JWT,允许用户访问该令牌允许的路由、服务和资源。由于 JWT 是无状态的,因此它们无需保留或同步令牌数据,从而能轻松实现扩展性。
- 信息交换:JWT 在各方之间安全地传输信息。因为如果使用公开/私密密钥对对 JWT 进行签名,就可以确保发送者是他们所声称的人。此外,由于签名部分是使用 header 和 payload 信息以及密钥计算出来的,因此还可以验证内容没有被篡改。
JWT 的使用流程
下面是一种典型的 JWT 使用流程:
- 用户登录:用户通过提供用户名和密码等凭据向服务器发出登录请求。
- 生成 JWT:服务器验证这些凭据,如果它们是正确的,然后将使用密钥对相关用户数据(如用户 ID、角色等)进行签名,以生成 JWT。
- 发送 JWT:服务器然后将此 JWT 发送回客户端,通常在响应体或响应头中。
- 存储 JWT:客户端接收到 JWT 后将其存储在某处,通常是在 Cookie 或 localStorage 中。
- 使用 JWT:之后,当客户端向服务器发出需要身份验证的请求时(例如获取用户特定的数据),它会在请求头的 Authorization 字段中包含此 JWT。格式通常是 Bearer YOUR_JWT_TOKEN。
- 验证 JWT:服务器将接收到的 JWT 解码并验证签名,确认这是否是在登录时由自己生成并发给该用户的 JWT。如果验证成功,则处理请求;如果验证失败(例如 JWT 无效或已过期),则返回错误。
刷新 JWT:在某些情况下,可以使用刷新令牌模式来获取新的 JWT,而不需要用户重新登录。这是一种安全性和持久登录的平衡方式。
JWT 与传统 Token 的区别
JWT 和传统的 token 都可用于身份验证,但它们在一些关键方面有所不同,并且 JWT 带来了一些明显的优势:
- 自包含性:JWT 是自包含的,因为它包含所有必要的信息,无需查询额外的数据源就可以验证和处理请求。这使得它在分布式系统中特别有用。
- 无状态性:JWT 是无状态的,这意味着服务器不需要存储任何会话信息。每次用户发送请求时,都会带上 JWT,其中包含所有需要验证身份的信息。
- 可扩展性:您可以在 JWT 的负载(payload)部分添加多种声明,以传递更多关于用户的信息或其他相关数据。
- 支持跨域:JWT 可用于不同域之间的身份验证,这对于微服务架构和跨域资源共享(CORS)非常有用。
- 性能:由于 JWT 自包含并且无状态,所以它可以减少数据库查找,从而提高应用程序的性能。
相比之下,传统的 token 通常是随机生成的字符串,存储在服务器端。当客户端发出请求时,服务器需要查找这个 token 才能验证用户。这可能导致额外的数据库查询,增加了复杂性并降低了性能。
JWT 过期问题的处理
处理 JWT 过期有几种常见的策略:
- 短期令牌 + 长期刷新令牌:此策略中使用两种 token。JWT 有效期设置得较短,用于授权用户访问资源。另外一个是长期的“刷新令牌”,用于在 JWT 过期后获取新的 JWT,而无需让用户重新登录。这种方式既提供了安全性(如果 JWT 被盗,它将很快过期),又保持了用户体验(即使 JWT 过期,用户也不需要重新输入用户名和密码)。
- 滑动会话:每次用户与应用程序交互时,都更新 JWT 的过期时间。这意味着只要用户保持活跃,他们的会话将永不过期。但是,如果用户一段时间内未进行任何活动,则他们的 JWT 将过期,他们必须重新登录。这种方法必须要小心处理,因为频繁生成新的 JWT 可能会带来性能问题。
- 提示用户重新登录:当 JWT 过期后,可以简单地向用户显示一个消息,提示他们需要重新登录。这是最简单的处理方式,但可能对用户体验产生负面影响,特别是如果 JWT 的有效期设置得非常短。
总的来说,如何处理 JWT 过期取决于您的具体应用和安全需求。不同的方法有各自的优点和缺点,您需要找到适合您场景的最佳解决方案。
JWT 的安全问题
如果 JWT 令牌被窃取,确实会引发安全问题。因为 JWT 是自包含的,也就是说它包含了关于用户的所有必要信息。一旦被窃取,攻击者可以使用此令牌访问和操作系统中应该由用户控制的数据和功能。
为了防止 JWT 被窃取,你可以采取以下几种策略:
- 使用 HTTPS:始终应在安全的 HTTPS 连接上发送 JWT,以防止网络监听。
- 设置过期时间:JWT 应该具有较短的过期时间。这样即使它们被窃取,攻击者只能在有限的时间内使用。
- 存储安全:如果在浏览器中使用 JWT,不应该将其存储在 localStorage 或 sessionStorage 中,因为这些地方容易受到 XSS 攻击。相反,应使用 HttpOnly cookie 存储 JWT,因为这种类型的 cookie 对脚本不可见,从而降低风险。
- 防止 CSRF:虽然使用 HttpOnly Cookie 可以防止 XSS,但可能会引入另一种攻击(CSRF)。你需要一个额外的 CSRF 令牌来防止这种攻击。
- 令牌无效化:您可以在服务器端实现黑名单机制,在 JWT 被盗或用户登出时废除 JWT。
- 不存储敏感数据:JWT 可以被解码获得其中的数据,因此不应该在其中存储敏感信息,如密码等。
虽然 JWT 可以增加应用程序的安全性,但它也引入了新的安全问题。理解这些问题并采取恰当的预防措施是非常重要的。
如何在PHP中使用JWT
要在 PHP 中使用 JWT 进行前后端通信,您需要首先通过 Composer 安装一个适合的库,例如,firebase/php-jwt。以下是如何生成和验证 JWT 的基本步骤:
-
首先,安装 firebase/php-jwt。在项目目录中运行以下命令:
composer require firebase/php-jwt -
生成 JWT 的示例代码如下:
- <?php
- require 'vendor/autoload.php';
- use Firebase\JWT\JWT;
- $key = "example_key";
- $payload = array(
- "iss" => "http://domain.com",
- "aud" => "http://domain.com",
- "iat" => time(),
- "nbf" => time() + 10,
- "exp" => time() + 3600,
- "data" => [
- "id" => 1,
- "email" => "admin@domain.com"
- ]
- );
- $jwt = JWT::encode($payload, $key);
- echo "Your token:\n" . print_r($jwt, true) . "\n";
- ?>
-
当您从客户端收到已签名的 JWT 后,可以使用下面的代码对其进行解码:
- <?php
- require 'vendor/autoload.php';
- use \Firebase\JWT\JWT;
- $key = "example_key";
- $jwt = $_GET["jwt"];
- try {
- $decoded = JWT::decode($jwt, $key, array('HS256'));
- print_r($decoded->data);
- } catch (\Exception $e) {
- echo 'Caught exception: ', $e->getMessage(), "\n";
- }
- ?>
这就是在 PHP 中使用 firebase/php-jwt 生成和解析 JWT 的基本步骤。对于实际的应用场景,您可能需要更详细地处理错误和异常,并确保安全性。
“短期令牌 + 长期刷新令牌” 的实现方式
"短期令牌 + 长期刷新令牌" 的策略是一种常用的处理 JWT 过期的方式。基本流程如下:
- 用户登录并验证凭证。
- 如果验证成功,服务器生成 JWT 和一个长期的“刷新令牌”。
- 服务器将 JWT 和刷新令牌发送到客户端。
- 当 JWT 过期后,客户端会发送包含刷新令牌的请求到后端,请求新的 JWT。
- 服务器接收到刷新令牌,验证其有效性,如果有效则发出新的 JWT。
以下是 Node.js 和 Express 实现 JWT 刷新机制的例子:
- // 登录路由
- app.post('/login', (req, res) => {
- const user = ... // 验证成功后,获取用户信息
- const token = jwt.sign({ _id: user._id }, private_key, { expiresIn: '15m' });
- const refreshToken = new RefreshToken({ token: jwt.sign({}, refresh_token_secret) });
- await refreshToken.save();
- res.json({ jwt: token, refreshToken: refreshToken.token });
- });
- // 刷新 JWT 路由
- app.post('/refresh', async (req, res) => {
- const refreshToken = req.body.token;
- if (!refreshToken) return res.sendStatus(401);
- const validToken = await RefreshToken.findOne({ token: refreshToken });
- if (!validToken) return res.sendStatus(403);
- const newToken = jwt.sign({ _id: user._id }, private_key, { expiresIn: '15m' });
- res.json({ jwt: newToken });
- });
这只是一个基本示例,实际实施可能需要更详细的错误处理和安全措施,例如处理刷新令牌的失效,防止刷新令牌被窃取等。
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。
在线投稿:投稿 站长QQ:1888636
后台-插件-广告管理-内容页尾部广告(手机) |