Toolypet
返回博客
Dev

JWT完全指南2026 - 结构、安全性和最佳实践总结

从JSON Web Token的结构和工作原理到安全漏洞和最佳实践。通过实战代码示例掌握JWT认证。

Toolypet Team

Toolypet Team

Development Team

6 分钟阅读

JWT完全指南2026

"JWT?不就是个令牌吗?"

没错。但是如果这个"令牌"实现不当,就会成为严重的安全问题。截至2026年,研究表明48%的AI生成认证代码存在安全漏洞

本指南将完整介绍从JWT结构到实战安全最佳实践的所有内容。


什么是JWT?

JWT(JSON Web Token)是一种紧凑且自包含的方式,用于在两方之间安全地传输信息。

主要特点

特点说明
Stateless服务器无需存储会话
Self-contained所需信息包含在令牌内
Compact可用于URL、HTTP头
Signed可检测篡改

使用场景

  • API认证:使用Bearer令牌进行API请求
  • SSO(单点登录):多服务间共享认证
  • 信息交换:传输签名数据
  • Authorization:包含权限/角色信息

JWT结构

JWT由三个用.(点)分隔的Base64URL编码部分组成。

xxxxx.yyyyy.zzzzz
  |      |      |
Header  Payload  Signature

1. Header

{
  "alg": "HS256",
  "typ": "JWT"
}
字段说明
alg签名算法(HS256、RS256等)
typ令牌类型(JWT)

2. Payload (Claims)

{
  "sub": "user123",
  "name": "张三",
  "email": "zhang@example.com",
  "role": "admin",
  "iat": 1708502400,
  "exp": 1708588800
}

注册声明(Registered Claims)

声明说明示例
iss签发者(Issuer)"https://auth.example.com"
sub主题(Subject)"user123"
aud受众(Audience)"https://api.example.com"
exp过期时间(Expiration)1708588800
nbf生效时间(Not Before)1708502400
iat签发时间(Issued At)1708502400
jtiJWT唯一ID"abc123"

公共/私有声明

{
  "role": "admin",           // 私有声明
  "email": "user@example.com",
  "permissions": ["read", "write"]
}

3. Signature

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

签名算法

对称密钥(Symmetric)

算法说明使用场景
HS256HMAC + SHA-256单服务器、简单实现
HS384HMAC + SHA-384更强的安全性
HS512HMAC + SHA-512最高级别安全性
// Node.js - HS256
const jwt = require('jsonwebtoken');

const token = jwt.sign(
  { userId: 123, role: 'admin' },
  'your-256-bit-secret',  // 使用相同密钥验证
  { algorithm: 'HS256', expiresIn: '1h' }
);

非对称密钥(Asymmetric)

算法说明使用场景
RS256RSA + SHA-256微服务、公开验证
RS384RSA + SHA-384高安全要求
RS512RSA + SHA-512最高级别安全性
ES256ECDSA + P-256短密钥、移动端
// RS256 - 非对称
const privateKey = fs.readFileSync('private.pem');
const publicKey = fs.readFileSync('public.pem');

// 签发(Private Key)
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });

// 验证(Public Key)
const decoded = jwt.verify(token, publicKey, { algorithms: ['RS256'] });

算法选择指南

场景推荐算法
单服务器HS256
微服务RS256
需要公开验证RS256 / ES256
移动端/IoTES256(短密钥)

实战实现

Node.js (Express)

const express = require('express');
const jwt = require('jsonwebtoken');

const app = express();
const SECRET = process.env.JWT_SECRET; // 从环境变量加载

// 签发令牌
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  // 用户验证(示例)
  const user = await verifyCredentials(email, password);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const token = jwt.sign(
    {
      sub: user.id,
      email: user.email,
      role: user.role,
    },
    SECRET,
    {
      expiresIn: '1h',
      issuer: 'your-app-name',
    }
  );

  res.json({ token });
});

// 中间件验证
const authMiddleware = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing token' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, SECRET, {
      issuer: 'your-app-name',
      algorithms: ['HS256'],
    });
    req.user = decoded;
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

// 受保护的路由
app.get('/protected', authMiddleware, (req, res) => {
  res.json({ message: `Hello, ${req.user.email}` });
});

Python (FastAPI)

from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from datetime import datetime, timedelta
import os

app = FastAPI()
security = HTTPBearer()
SECRET = os.environ.get("JWT_SECRET")

# 签发令牌
def create_token(user_id: str, role: str) -> str:
    payload = {
        "sub": user_id,
        "role": role,
        "iat": datetime.utcnow(),
        "exp": datetime.utcnow() + timedelta(hours=1),
    }
    return jwt.encode(payload, SECRET, algorithm="HS256")

# 验证令牌
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECRET, algorithms=["HS256"])
        return payload
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

# 受保护的端点
@app.get("/protected")
def protected_route(user: dict = Depends(verify_token)):
    return {"message": f"Hello, {user['sub']}"}

安全漏洞与对策

1. Algorithm None攻击

// 恶意header
{
  "alg": "none",
  "typ": "JWT"
}

对策

// 明确指定允许的算法
jwt.verify(token, secret, { algorithms: ['HS256'] }); // 正确
jwt.verify(token, secret); // 危险

2. 弱密钥

// 危险
const SECRET = 'secret123';

// 安全(至少256位)
const SECRET = crypto.randomBytes(32).toString('hex');

3. 敏感信息泄露

// 危险:Payload可被Base64解码
{
  "password": "user_password",  // 绝对禁止!
  "creditCard": "1234-5678-9012-3456"
}

// 安全
{
  "sub": "user123",
  "role": "admin"
}

4. 令牌盗用

应对策略

  • 短过期时间(15分钟~1小时)
  • 使用Refresh Token
  • HTTPS必须
  • HttpOnly Cookie(防XSS)

5. JWT重放攻击(Replay Attack)

// 使用jti(JWT ID)
const token = jwt.sign({
  sub: user.id,
  jti: crypto.randomUUID(), // 唯一ID
}, SECRET);

// 服务器管理jti黑名单

Access Token + Refresh Token

为什么需要?

令牌有效期用途
Access Token15分钟~1小时API认证
Refresh Token7天~30天刷新Access Token

实现示例

// 登录时签发两个令牌
app.post('/login', async (req, res) => {
  const user = await verifyCredentials(req.body);

  const accessToken = jwt.sign(
    { sub: user.id, type: 'access' },
    ACCESS_SECRET,
    { expiresIn: '15m' }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, type: 'refresh' },
    REFRESH_SECRET,
    { expiresIn: '7d' }
  );

  // Refresh Token使用HttpOnly Cookie
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000,
  });

  res.json({ accessToken });
});

// 刷新令牌
app.post('/refresh', (req, res) => {
  const refreshToken = req.cookies.refreshToken;

  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);

    const newAccessToken = jwt.sign(
      { sub: decoded.sub, type: 'access' },
      ACCESS_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

JWT调试

令牌解码

JWT Decoder中粘贴令牌可以:

  • 查看Header
  • 查看Payload
  • 查看过期时间
  • 验证签名(输入密钥时)

CLI解码

# 解码Header
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" | base64 -d

# 解码Payload
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4ifQ" | base64 -d

最佳实践清单

签发

  • 强密钥(256位以上)
  • 明确算法(HS256、RS256)
  • 短过期时间(15分钟~1小时)
  • 包含必要声明(sub、iat、exp、iss)
  • 不包含敏感信息

验证

  • 算法白名单(algorithms: ['HS256']
  • 过期时间验证
  • 签发者(iss)验证
  • 受众(aud)验证

存储与传输

  • HTTPS必须
  • HttpOnly Cookie(防XSS)
  • SameSite属性(防CSRF)
  • 避免localStorage(XSS脆弱)

常见问题

Q1: JWT vs Session,何时使用哪个?

A

  • JWT:无状态API、微服务、移动应用
  • Session:传统Web应用、服务端渲染、需要即时登出

Q2: JWT被盗怎么办?

A:Access Token在过期前有效。因此:

  • 设置短过期时间(15分钟)
  • Refresh Token Rotation
  • 怀疑时使所有令牌失效

Q3: 为什么不应该存储在localStorage?

A:容易受到XSS攻击。由于JavaScript可以访问,恶意脚本可能窃取令牌。HttpOnly Cookie更安全。

Q4: RS256比HS256更安全吗?

A:不是"更安全"而是"用途不同"。

  • HS256:共享相同密钥、单服务器
  • RS256:用公钥验证、多服务

Q5: 需要令牌黑名单吗?

A:如果需要即时登出则需要。但这会减少无状态的优势。短过期时间 + Refresh Token是替代方案。


总结

JWT安全要点:

  1. 强密钥:256位以上随机
  2. 算法验证:白名单方式
  3. 短有效期:Access 15分钟、Refresh 7天
  4. 安全存储:HttpOnly + Secure Cookie
  5. 排除敏感信息:Payload任何人都能解码

相关工具

工具用途
JWT DecoderJWT解码和验证
Base64 EncoderBase64编码/解码
Hash Generator哈希生成
JWT认证安全APIWeb开发令牌

关于作者

Toolypet Team

Toolypet Team

Development Team

The Toolypet Team creates free, privacy-focused web tools for developers and designers. All tools run entirely in your browser with no data sent to servers.

Web DevelopmentCSS ToolsDeveloper ToolsSEOSecurity