zl程序教程

您现在的位置是:首页 >  前端

当前栏目

什么是 JSON Web 令牌 (JWT)?为什么 API 使用它们?

WebJSONJSONAPI 什么 为什么 JWT 它们
2023-09-27 14:27:53 时间

在这里插入图片描述
JSON Web Tokens (JWT) 标准描述了一种用于可验证数据传输的紧凑方法。每个令牌都包含一个签名,允许发布方检查消息的完整性。

在本文中,您将了解 JWT 结构包含的内容以及如何生成自己的令牌。JWT 是一种保护 API 和验证用户会话的流行方法,因为它们简单且独立。

JWT 的工作原理

任何 API 中最常见的任务之一是验证用户是否是他们声称的身份。身份验证通常通过让客户端在发送到服务器的请求中包含一个 API 密钥来处理。密钥包含识别用户的嵌入信息。这仍然留下了一个大问题:服务器如何验证它是否首先发布了密钥?

JWT 通过使用秘密对每个令牌进行签名来方便地解决这个问题。服务器可以通过尝试使用其私有秘密重新计算提供的签名来检查令牌的有效性。任何篡改都会导致验证失败。

JWT 格式

JWT 由三个不同的组件组成:

  • 标头- 这包括有关令牌本身的元数据,例如使用的签名算法。
  • 有效负载——令牌的有效负载可以是与您的系统相关的任意数据。它可以包括用户的 ID 和他们可以交互的功能列表。
  • 签名– 签名允许将来验证令牌的完整性。它是通过使用只有服务器知道的秘密值对标头和有效负载进行签名来创建的。

这三个组件与句点相结合以生成 JWT:

header.payload.signature

每一段都使用 Base-64 编码。完整的令牌是一串文本,可以在编程环境中轻松使用并与 HTTP 请求一起发送。

创建 JWT

创建 JWT 的步骤可以在所有编程语言中实现。此示例使用 PHP,但在您自己的系统中该过程将类似。

从创建标题开始。这通常包括两个字段,alg和typ:

  • alg– 将用于创建签名的散列算法。这通常是 HMAC SHA256 ( HS256)。
  • typ– 正在生成的令牌类型。这应该是JWT。

这是定义标头的 JSON:

{
    "alg": "HS256",
    "typ": "JWT"
}

接下来需要对标头 JSON 进行 Base64 编码:

$headerData = ["alg" => "HS256", "typ" => "JWT"];
$header = base64_encode(json_encode($headerData));

接下来将您的令牌的有效负载定义为另一个 JSON 对象。这是特定于应用程序的。该示例提供了经过身份验证的用户帐户的详细信息,以及有关令牌本身的信息。exp, iat, 和nbf是按约定用于表示令牌到期时间的字段,在时间发出,在(开始)时间之前无效。有效载荷也需要进行 Base64 编码。

$payloadData = [
    "userId" => 1001,
    "userName" => "demo",
    "licensedFeatures" => ["todos", "calendar", "invoicing"],
    "exp" => (time() + 900),
    "iat" => time(),
    "nbf" => time()
];
$payload = base64_encode(json_encode($payloadData));

剩下的就是创建签名。为了产生这个,您首先将标头和有效负载组合成一个由字符分隔的单个字符串.:

$headerAndPayload = "$header.$payload";

接下来,您必须生成一个唯一密钥以用作您的签名密钥。秘密需要安全地存储在您的服务器上,并且绝不能发送给客户端。暴露此值将允许任何人创建有效令牌。

// PHP method to generate 32 random characters
$secret = bin2hex(openssl_random_pseudo_bytes(16));

您可以使用您在标头中指定的散列算法使用密钥对组合的标头和有效负载字符串进行签名,从而完成该过程。输出签名必须像其他组件一样采用 Base64 编码。

$signature = base64_encode(hash_hmac("sha256", $headerAndPayload, $secret, true));

现在,您已将标头、有效负载和签名作为单独的文本组件。将它们与分隔符连接在一起.以创建 JWT 以发送给您的客户端:

$jwt = "$header.$payload.$signature";

验证传入的 JWT

客户端应用程序可以通过解码令牌的有效负载来确定用户可用的功能。这是 JavaScript 中的一个示例:

const tokenComponents = jwt.split(".");
const payload = token[1];
const payloadDecoded = JSON.parse(atob(payload));
 
// ["todos", "calendar", "invoicing"]
console.log(payloadDecoded.licensedFeatures);

攻击者可能会意识到这些数据是纯文本并且看起来很容易修改。他们可以尝试通过在下一个请求中更改令牌的有效负载来说服服务器他们拥有奖励功能:

// Create a new payload component
const modifiedPayload = btoa(JSON.stringify({
    ...payloadDecoded,
    licensedFeatures: ["todos", "calendar", "invoicing", "extraPremiumFeature"]
}));
 
// Stitch the JWT back together with the original header and signature
const newJwt = `${token[0]}.${modifiedPayload}.${token[2]}`

服务器如何防御这些攻击的答案在于生成签名的方法。签名值考虑了令牌的标头和有效负载。如本例所示,修改有效负载意味着签名不再有效。

服务器端代码通过重新计算其签名来验证传入的 JWT。如果客户端发送的签名与服务器上生成的值不匹配,则令牌已被篡改。

$tamperedToken = $_POST["apiKey"];
list($header, $payload, $signature) = $tamperedToken;
 
// Determine the signature this token *should* have 
// when the server's secret is used as the key
$expectedSignature = hash_hmac("sha256", "$header.$payload", $secret, true);
 
// The token has been tampered with because its 
// signature is incorrect for the data it includes
if ($signature !== $expectedSignature) {
    http_response_code(403);
}
// The signatures match - we generated this 
// token and can safely trust its data
else {
    $user = fetchUserById($payload["userId"]);
}

攻击者不可能在不访问服务器机密的情况下生成有效令牌。这也意味着秘密的意外丢失或故意轮换将立即使所有先前发布的令牌无效。

在实际情况中,您的身份验证代码还应检查令牌有效负载中的过期和“不早于”时间戳。这些用于确定用户的会话是否仍然有效。

何时使用 JWT

JWT 经常用于 API 身份验证,因为它们可以直接在服务器上实现,易于在客户端使用,并且易于跨网络边界传输。尽管它们很简单,但它们具有良好的安全性,因为每个令牌都使用服务器的密钥进行签名。
JWT 是一种无状态机制,因此您无需在服务器上记录有关已发行令牌的信息。您可以从令牌的有效负载中获取有关呈现 JWT 的客户端的信息,而不必在数据库中执行查找。一旦您验证了令牌的签名,就可以安全地信任此信息。

每当您需要在两方之间交换信息而没有被篡改的风险时,使用 JWT 是一个不错的选择。但是有一些弱点需要注意:如果您的服务器的密钥泄露,或者您的签名验证码包含错误,整个系统将受到损害。出于这个原因,许多开发人员选择使用开源库来实现 JWT 生成和验证。选项适用于所有流行的编程语言。当您自己验证令牌时,它们消除了监督的风险。

概括

JWT 标准是一种包含内置完整性验证的数据交换格式。JWT 通常用于保护 API 服务器和客户端应用程序之间的交互。如果服务器能够复制它们的签名,它就可以信任传入的令牌。这允许使用从令牌的有效负载中获得的信息安全地执行操作。

JWT 很方便,但也有一些缺点。如果您拥有多个有效负载字段,JWT 的 Base64 编码的文本表示会很快变大。当您的客户端需要随每个请求发送 JWT 时,这可能会成为不可接受的开销。

JWT 的无状态性也是另一个潜在的缺点:一旦发行,令牌就是不可变的,并且必须按原样使用,直到它们过期。使用 JWT 有效负载来确定用户权限或许可功能的客户端将需要在其分配发生更改时从后端获取新令牌。