JWT (JSON Web Token) 是一种开放标准(RFC 7519),用于安全地在各方之间传输信息。JWT 是一种自包含的令牌,通常用于身份验证和信息交换。它可以有效地在客户端和服务器之间传输身份信息,而不需要服务器存储会话状态。

JWT 主要由三部分组成:

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

JWT 的结构

一个完整的 JWT 是由三个部分组成的,它们用 . 分隔,结构如下:

header.payload.signature

1. Header

Header 部分通常由两部分组成:

  • alg(算法):签名所使用的算法(如 HMAC SHA256 或 RSA)。
  • typ(类型):通常为 JWT

示例:

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

2. Payload

Payload 部分包含了要传输的数据(即声明)。JWT 可以包含三种类型的声明:

  • 注册声明:如 sub(主题)、exp(过期时间)、iat(签发时间)等。
  • 公共声明:自定义的数据声明,应该避免冲突。
  • 私有声明:用于双方之间共享的信息。

示例:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

3. Signature

签名用于验证消息是否未被篡改,并且用于验证消息的来源。为了创建签名,需要使用 Header 和 Payload 的内容,并用一个密钥(secret)和指定的算法(如 HMAC SHA256)来生成。

签名的生成方法如下:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret)

JWT 的生成与验证流程

1. 创建 JWT

为了生成 JWT,你需要做以下几步:

  1. 创建 Header 和 Payload。
  2. 使用密钥生成签名。
  3. 将 Header、Payload 和 Signature 使用 . 连接起来。

示例代码(使用 JavaScript 生成 JWT):

// 创建 Header
const header = {
  alg: "HS256",
  typ: "JWT"
};

// 创建 Payload
const payload = {
  sub: "1234567890",
  name: "John Doe",
  iat: Math.floor(Date.now() / 1000) // 当前时间戳
};

// 创建密钥
const secret = 'your-256-bit-secret';

// Base64Url 编码函数
function base64UrlEncode(input) {
  return Buffer.from(input).toString('base64')
    .replace(/=/g, '')        // 去掉 padding(=)
    .replace(/\+/g, '-')      // 替换 +
    .replace(/\//g, '_');     // 替换 /
}

// 编码 Header 和 Payload
const encodedHeader = base64UrlEncode(JSON.stringify(header));
const encodedPayload = base64UrlEncode(JSON.stringify(payload));

// 创建签名
const crypto = require('crypto');
const signature = crypto
  .createHmac('sha256', secret)
  .update(encodedHeader + '.' + encodedPayload)
  .digest('base64')
  .replace(/=/g, '')
  .replace(/\+/g, '-')
  .replace(/\//g, '_');

// 最终生成 JWT
const jwt = `${encodedHeader}.${encodedPayload}.${signature}`;

console.log(jwt);

2. 验证 JWT

验证 JWT 主要包括以下几个步骤:

  1. 解码 Header 和 Payload。
  2. 使用相同的算法和密钥重新计算签名。
  3. 将计算出来的签名与 JWT 中的签名进行比较。

示例代码(使用 JavaScript 验证 JWT):

function verifyJWT(token, secret) {
  // 分解 token
  const [encodedHeader, encodedPayload, signature] = token.split('.');

  // 重新计算签名
  const recalculatedSignature = crypto
    .createHmac('sha256', secret)
    .update(encodedHeader + '.' + encodedPayload)
    .digest('base64')
    .replace(/=/g, '')
    .replace(/\+/g, '-')
    .replace(/\//g, '_');

  // 比较签名
  return recalculatedSignature === signature;
}

const isValid = verifyJWT(jwt, 'your-256-bit-secret');
console.log(isValid ? "JWT is valid!" : "JWT is invalid!");

3. 使用 JWT 的常见场景

  • 身份验证:当用户登录时,服务器创建 JWT 并将其发送到客户端,客户端将其保存在本地存储(如 localStorage 或 cookie)中。每次请求时,客户端将 JWT 包含在请求头中,服务器验证其有效性后提供相应的资源。
  • 信息交换:由于 JWT 是自包含的,它不仅可以用于身份验证,还可以用于在系统间交换信息。

4. 使用 JWT 与服务器端

PHP 中使用 JWT

在 PHP 中,使用第三方库来生成和验证 JWT 是最常见的做法。例如,firebase/php-jwt 库是一个流行的选择。

安装 firebase/php-jwt
composer require firebase/php-jwt
生成 JWT:
<?php
use \Firebase\JWT\JWT;

$key = "your_secret_key";

// 构建 payload
$payload = array(
  "iss" => "http://example.org", // 签发者
  "aud" => "http://example.com", // 接收者
  "iat" => time(),               // 签发时间
  "exp" => time() + 3600         // 过期时间
);

// 生成 JWT
$jwt = JWT::encode($payload, $key);

echo $jwt;
?>
验证 JWT:
<?php
use \Firebase\JWT\JWT;

$key = "your_secret_key";
$jwt = $_GET['jwt']; // 从请求中获取 JWT

try {
  $decoded = JWT::decode($jwt, $key, array('HS256'));
  print_r($decoded);
} catch (Exception $e) {
  echo 'Caught exception: ',  $e->getMessage();
}
?>

5. JWT 的优缺点

优点:

  • 无状态:JWT 是自包含的,不需要存储在服务器端,减少了服务器的负担。
  • 可跨域:由于其结构化的特性,可以轻松地跨不同的域和服务传递和验证。
  • 灵活性:JWT 适用于身份验证、信息交换、API 密钥等场景。

缺点:

  • 不易撤销:JWT 不能被直接撤销,除非你在服务端维护一个“黑名单”。
  • 过期问题:JWT 具有过期时间,可能需要处理过期和刷新 token 的问题。
  • 安全问题:如果使用弱的密钥或不安全的算法,可能导致安全漏洞。

6. 总结

JWT 是一种强大的机制,广泛用于 Web 应用中的身份验证和信息交换。它的自包含特性使得它不需要服务器存储会话信息,并且可以灵活地跨域和跨平台使用。然而,开发时应考虑到安全性,使用强密钥和合适的加密算法来确保其安全性。

如果你有关于 JWT 的更详细问题或需要其他实现的例子,随时告诉我!