什么是 JWT?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在各方之间安全传输信息的紧凑、URL 安全的方式。JWT 通常用于身份验证和信息交换,广泛应用于单点登录(SSO)、API 认证等场景。

JWT 由三部分组成,用点号(.)分隔:Header(头部)、Payload(载荷)和 Signature(签名)。每部分都是 Base64Url 编码的 JSON 对象。

JWT 结构解析

xxxxx.yyyyy.zzzzz
Header(头部)Base64Url
{
  "alg": "HS256",
  "typ": "JWT"
}

指定令牌类型和签名算法

Payload(载荷)Base64Url
{
  "sub": "1234567890",
  "name": "张三",
  "iat": 1516239022
}

包含声明(Claims)数据

Signature(签名)加密
HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

验证令牌完整性

JWT 标准声明

声明名称说明
issIssuer令牌签发者
subSubject令牌主题(通常是用户ID)
audAudience接收令牌的一方
expExpiration过期时间(Unix 时间戳)
nbfNot Before生效时间
iatIssued At签发时间
jtiJWT ID令牌唯一标识

支持的签名算法

HS256对称加密

HMAC SHA-256,使用相同密钥签名和验证

速度快密钥共享
HS384对称加密

HMAC SHA-384,更长的哈希输出

更安全密钥共享
RS256非对称加密

RSA SHA-256,私钥签名公钥验证

推荐密钥分离
ES256非对称加密

ECDSA SHA-256,椭圆曲线签名

最安全短签名

编程语言中使用 JWT

JavaScript / Node.js

const jwt = require('jsonwebtoken');

// 生成 JWT
const token = jwt.sign(
  { userId: 123, name: '张三' },
  'your-secret-key',
  { expiresIn: '1h' }
);

// 验证并解码
try {
  const decoded = jwt.verify(token, 'your-secret-key');
  console.log(decoded); // { userId: 123, name: '张三', iat: ..., exp: ... }
} catch (err) {
  console.log('Token 无效或已过期');
}

// 仅解码(不验证签名)
const decoded = jwt.decode(token);

// 使用 RS256(非对称)
const privateKey = fs.readFileSync('private.key');
const publicKey = fs.readFileSync('public.key');
const token = jwt.sign({ data: 'payload' }, privateKey, { algorithm: 'RS256' });
const verified = jwt.verify(token, publicKey);

Python

import jwt
import datetime

# 生成 JWT
payload = {
    'user_id': 123,
    'name': '张三',
    'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
}
token = jwt.encode(payload, 'your-secret-key', algorithm='HS256')

# 验证并解码
try:
    decoded = jwt.decode(token, 'your-secret-key', algorithms=['HS256'])
    print(decoded)
except jwt.ExpiredSignatureError:
    print('Token 已过期')
except jwt.InvalidTokenError:
    print('Token 无效')

# 仅解码(不验证)
decoded = jwt.decode(token, options={"verify_signature": False})

# 使用 RS256
with open('private.pem', 'rb') as f:
    private_key = f.read()
token = jwt.encode({'data': 'payload'}, private_key, algorithm='RS256')

Java

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import javax.crypto.SecretKey;

// 生成密钥
SecretKey key = Keys.secretKeyFor(SignatureAlgorithm.HS256);

// 生成 JWT
String token = Jwts.builder()
    .setSubject("1234567890")
    .claim("name", "张三")
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + 3600000))
    .signWith(key)
    .compact();

// 验证并解码
try {
    Claims claims = Jwts.parserBuilder()
        .setSigningKey(key)
        .build()
        .parseClaimsJws(token)
        .getBody();
    String subject = claims.getSubject();
} catch (ExpiredJwtException e) {
    System.out.println("Token 已过期");
} catch (JwtException e) {
    System.out.println("Token 无效");
}

PHP

use Firebase\JWT\JWT;
use Firebase\JWT\Key;

// 生成 JWT
$payload = [
    'user_id' => 123,
    'name' => '张三',
    'iat' => time(),
    'exp' => time() + 3600
];
$token = JWT::encode($payload, 'your-secret-key', 'HS256');

// 验证并解码
try {
    $decoded = JWT::decode($token, new Key('your-secret-key', 'HS256'));
    print_r($decoded);
} catch (\Firebase\JWT\ExpiredException $e) {
    echo 'Token 已过期';
} catch (\Exception $e) {
    echo 'Token 无效';
}

// 仅解码(不验证)
$tks = explode('.', $token);
$payload = json_decode(base64_decode($tks[1]));

Go

import (
    "github.com/golang-jwt/jwt/v5"
    "time"
)

// 生成 JWT
claims := jwt.MapClaims{
    "user_id": 123,
    "name":    "张三",
    "exp":     time.Now().Add(time.Hour).Unix(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, _ := token.SignedString([]byte("your-secret-key"))

// 验证并解码
parsed, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return []byte("your-secret-key"), nil
})
if claims, ok := parsed.Claims.(jwt.MapClaims); ok && parsed.Valid {
    fmt.Println(claims["name"])
}

// 使用结构体声明
type MyClaims struct {
    UserID int    `json:"user_id"`
    Name   string `json:"name"`
    jwt.RegisteredClaims
}

C#

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;

// 生成 JWT
var claims = new[] {
    new Claim("user_id", "123"),
    new Claim("name", "张三")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key"));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

var token = new JwtSecurityToken(
    claims: claims,
    expires: DateTime.Now.AddHours(1),
    signingCredentials: creds
);
string tokenString = new JwtSecurityTokenHandler().WriteToken(token);

// 验证并解码
var handler = new JwtSecurityTokenHandler();
try {
    var principal = handler.ValidateToken(tokenString, 
        new TokenValidationParameters {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = key,
            ValidateLifetime = true
        }, out _);
} catch (SecurityTokenException) {
    Console.WriteLine("Token 无效");
}

Ruby

require 'jwt'

# 生成 JWT
payload = { user_id: 123, name: '张三' }
token = JWT.encode(payload, 'your-secret-key', 'HS256')

# 验证并解码
begin
  decoded, header = JWT.decode(token, 'your-secret-key', true, algorithm: 'HS256')
  puts decoded
rescue JWT::ExpiredSignature
  puts 'Token 已过期'
rescue JWT::DecodeError
  puts 'Token 无效'
end

# 仅解码(不验证)
decoded = JWT.decode(token, nil, false)

# 使用 RS256
private_key = OpenSSL::PKey::RSA.new(File.read('private.pem'))
token = JWT.encode(payload, private_key, 'RS256')

Shell / 命令行

# 解码 JWT(仅查看内容,不验证签名)
# Header
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d
# 输出: {"alg":"HS256","typ":"JWT"}

# Payload
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IuW8oOS4iSIsImlhdCI6MTUxNjIzOTAyMn0" | base64 -d
# 输出: {"sub":"1234567890","name":"张三","iat":1516239022}

# 使用 jq 解析
echo "eyJzdWIiOiIxMjM0NTY3ODkwIn0" | base64 -d | jq .

# 使用 jwt-cli 工具
jwt decode your.jwt.token

# 使用 Python 快速解码
python3 -c "
import base64, json
token = 'your.jwt.token'
parts = token.split('.')
print(json.loads(base64.urlsafe_b64decode(parts[1] + '==')))
"

JWT 常见应用场景

🔐 用户认证

用户登录后颁发 Token,后续请求携带 Token 验证身份

🔄 单点登录(SSO)

跨系统身份认证,一次登录多处使用

📱 移动应用

移动端 API 认证,无状态会话管理

🔗 API 网关

微服务架构中的统一认证授权

📧 邮件验证

注册确认、密码重置等一次性令牌

🛒 电商订单

订单状态、支付信息的临时凭证

安全注意事项

⚠️ 不要在 Payload 中存储敏感信息

JWT 的 Payload 只是 Base64 编码,不是加密,任何人都可以解码查看。

⚠️ 使用强密钥

对称加密(HS256)密钥长度至少 256 位,建议使用随机生成的强密钥。

✓ 设置合理的过期时间

短期 Token 配合 Refresh Token 机制,避免 Token 长期有效。

✓ 生产环境使用 HTTPS

防止 Token 在传输过程中被截获。

✓ 非对称算法更安全

RS256/ES256 支持公钥分发、私钥保管,适合分布式系统。

✓ 验证所有声明

验证 exp(过期时间)、iss(签发者)、aud(接收者)等声明。

常见问题

Q: JWT 和 Session 有什么区别?

A: Session 存储在服务器端,需要服务器维护状态;JWT 是无状态的,所有信息存储在 Token 中。JWT 更适合分布式系统和微服务架构,但无法主动使 Token 失效。

Q: JWT 过期了怎么办?

A: 使用 Refresh Token 机制。Access Token 有效期短(如 15 分钟),Refresh Token 有效期长(如 7 天),用于获取新的 Access Token。

Q: 如何使 JWT 失效?

A: JWT 本身无法主动失效。常用方案:1) 维护 Token 黑名单;2) 使用短有效期 + Refresh Token;3) 在 Payload 中加入版本号,验证时检查版本。

Q: HS256 和 RS256 该选哪个?

A: 单体应用可以用 HS256(对称加密),简单高效。微服务、开放 API 推荐用 RS256(非对称加密),私钥签名、公钥验证,更安全且便于密钥管理。