CodeL
以前端为翼,以 AI 为脑,向全栈而行
2026-03-31

JWT 完全指南

JWT 完全指南 零基础也能看懂的 JWT 教程,从概念到实战,Java 和 Node.js 双语言实现,覆盖登录认证、API 授权等真实场景 目录 1. 核心概念 2. JWT 结构详解 3. JWT 工作流程 4....

JWT 完全指南 #

零基础也能看懂的 JWT 教程,从概念到实战,Java 和 Node.js 双语言实现,覆盖登录认证、API 授权等真实场景


目录 #

  1. 核心概念
  2. JWT 结构详解
  3. JWT 工作流程
  4. Node.js 实现
  5. Java 实现
  6. 实战场景
  7. 安全最佳实践
  8. JWT vs Session
  9. 常见问题
  10. 总结速记

一、核心概念 #

1.1 什么是 JWT?(大白话) #

JWT(JSON Web Token) 是一种用于在网络上安全传输信息的"通行证"。

打个比方:

  • 传统 Session = 你的身份证存在派出所,每次办事都要派出所确认
  • JWT = 身份证发给你自己保管,上面有防伪标记,办事时直接出示
传统 Session 流程:
用户登录 → 服务器创建 Session → 返回 Session ID
用户请求 → 携带 Session ID → 服务器查找 Session → 验证身份
 
JWT 流程:
用户登录 → 服务器生成 JWT → 返回给用户
用户请求 → 携带 JWT → 服务器验证签名 → 直接信任(无需存储)

1.2 为什么需要 JWT? #

传统 Session 的问题:

问题 说明
服务器压力 每个用户都要存 Session,用户多了内存爆炸
扩展困难 多台服务器要共享 Session,需要额外存储(Redis)
跨域问题 前后端分离、移动端、小程序,Cookie 不好用
移动端不友好 原生 App 不支持 Cookie

JWT 的优势:

优势 说明
无状态 服务器不需要存储,JWT 自带信息
跨平台 Web、App、小程序都可以用
易扩展 水平扩展不需要考虑 Session 同步
跨域友好 放在 Header 里,不依赖 Cookie

1.3 JWT 的典型应用场景 #

场景 说明
用户认证 登录后发放 Token,后续请求携带验证
信息交换 安全地在各方之间传输信息(有签名防篡改)
单点登录(SSO) 一次登录,多个系统共享认证
API 授权 第三方应用访问 API
无状态服务 微服务架构中传递用户信息

1.4 核心名词解释 #

名词 大白话解释
Token 令牌,一串加密字符串,用于证明身份
Payload 载荷,JWT 中存储的实际数据(用户 ID、过期时间等)
Signature 签名,用密钥生成的防伪标记,验证 JWT 没被篡改
Secret 密钥,服务器保管,用于签名和验证
Claim 声明,Payload 中的每个字段叫一个 Claim
Expiration 过期时间,JWT 的有效期
Refresh Token 刷新令牌,用于获取新的 Access Token

二、JWT 结构详解 #

2.1 JWT 的三部分结构 #

JWT 由三部分组成,用 . 分隔:

xxxxx.yyyyy.zzzzz
 
Header.Payload.Signature
 
示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

2.2 Header(头部) #

Header 描述 JWT 的元信息:

{
  "alg": "HS256",  // 签名算法:HS256、RS256、ES256 等
  "typ": "JWT"     // 令牌类型:固定为 JWT
}

Base64 编码后:

const header = { alg: "HS256", typ: "JWT" };
const encoded = btoa(JSON.stringify(header));
// 结果:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

常用签名算法:

算法 类型 特点
HS256 对称加密 用同一个密钥签名和验证,速度快
RS256 非对称加密 私钥签名,公钥验证,更安全
ES256 非对称加密 椭圆曲线,密钥更短,性能更好

2.3 Payload(载荷) #

Payload 存储Claim(声明):

{
  // 标准声明(Registered Claims)
  "iss": "myapp.com",           // 签发者
  "sub": "1234567890",          // 主题(通常是用户 ID)
  "aud": "myapp.com",           // 接收者
  "exp": 1516239022,            // 过期时间
  "nbf": 1516239022,            // 生效时间
  "iat": 1516239022,            // 签发时间
  "jti": "unique-id",           // JWT ID(唯一标识)
  
  // 自定义声明
  "name": "John Doe",
  "role": "admin",
  "email": "john@example.com"
}

标准声明说明:

声明 全称 说明
iss Issuer 签发 JWT 的主体
sub Subject JWT 的主题(用户 ID)
aud Audience JWT 的接收者
exp Expiration 过期时间(时间戳)
nbf Not Before 生效时间(在此之前无效)
iat Issued At 签发时间
jti JWT ID JWT 的唯一标识(用于防止重放攻击)

⚠️ 注意:Payload 不是加密的,只是 Base64 编码,不要存敏感信息!

// ❌ 错误:不要在 Payload 中存敏感信息
{
  "password": "123456",      // 任何人都能看到!
  "creditCard": "xxxx-xxxx"  // 危险!
}
 
// ✅ 正确:只存非敏感的用户标识
{
  "userId": "12345",
  "role": "admin"
}

2.4 Signature(签名) #

签名用于验证 JWT 没有被篡改:

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

签名过程:

// 伪代码
const headerEncoded = base64UrlEncode(header);
const payloadEncoded = base64UrlEncode(payload);
const unsignedToken = headerEncoded + "." + payloadEncoded;
 
// 用密钥签名
const signature = HMACSHA256(unsignedToken, secret);
 
// 最终 JWT
const jwt = unsignedToken + "." + base64UrlEncode(signature);

为什么签名能防篡改?

攻击者想修改 Payload(比如把 role 从 user 改成 admin):
 
1. 修改 Payload:{ "role": "admin" }
2. Base64 编码新 Payload
3. 重新组合 JWT
 
问题:攻击者没有密钥,无法生成正确的签名!
服务器验证签名失败 → 拒绝请求

2.5 Base64Url 编码 #

JWT 使用 Base64Url 编码(不是普通 Base64):

// Base64: + / =
// Base64Url: - _ (去掉填充)
 
// 普通Base64
"abc+def/ghi="
 
// Base64Url(JWT 用这个)
"abc-def_ghi"
 
// 转换函数
function base64UrlEncode(str) {
  return btoa(str)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
}
 
function base64UrlDecode(str) {
  str = str.replace(/-/g, '+').replace(/_/g, '/');
  while (str.length % 4) str += '=';
  return atob(str);
}

三、JWT 工作流程 #

3.1 登录认证流程 #

┌─────────────────────────────────────────────────────────────┐
│                    JWT 登录认证流程                          │
└─────────────────────────────────────────────────────────────┘
 
用户                     服务器
  │                         │
  │ ① 登录请求               │
  │    POST /login           │
  │    {username, password}  │
  │────────────────────────>│
  │                         │ ② 验证用户名密码
  │                         │    查询数据库
  │                         │
  │                         │ ③ 生成 JWT
  │                         │    签名 + 设置过期时间
  │                         │
  │ ④ 返回 JWT               │
  │<────────────────────────│
  │    {token: "xxx.yyy.zzz"}│
  │                         │
  │ ⑤ 存储 JWT               │
  │    localStorage         │
  │                         │
  │ ⑥ 业务请求               │
  │    GET /api/user        │
  │    Header: Authorization: Bearer xxx.yyy.zzz
  │────────────────────────>│
  │                         │ ⑦ 验证 JWT
  │                         │    - 解析 JWT
  │                         │    - 验证签名
  │                         │    - 检查过期时间
  │                         │
  │                         │ ⑧ 返回数据
  │<────────────────────────│
  │    {user: {...}}        │

3.2 Token 刷新流程 #

┌─────────────────────────────────────────────────────────────┐
│                    Token 刷新流程                            │
└─────────────────────────────────────────────────────────────┘
 
Access Token:有效期短(15-30分钟)
Refresh Token:有效期长(7-30天)
 
用户                     服务器
  │                         │
  │ 业务请求                 │
  │ Header: Bearer access_token
  │────────────────────────>│
  │                         │
  │ 返回:Token 过期         │
  │<────────────────────────│
  │    {code: 401, message: "token expired"}
  │                         │
  │ 刷新 Token              │
  │ POST /refresh           │
  │ {refresh_token}         │
  │────────────────────────>│
  │                         │ 验证 refresh_token
  │                         │ 生成新的 access_token
  │                         │
  │ 返回新的 Token          │
  │<────────────────────────│
  │    {access_token, refresh_token}
  │                         │
  │ 用新 Token 重新请求      │
  │────────────────────────>│

3.3 JWT 在请求中的传递 #

方式 1:Authorization Header(推荐)

GET /api/user HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
 
// 注意:Bearer 前缀是标准写法

方式 2:URL Query(不推荐)

GET /api/user?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... HTTP/1.1
 
// 问题:Token 会被记录在服务器日志、浏览器历史中

方式 3:Cookie(可行但有限制)

GET /api/user HTTP/1.1
Host: api.example.com
Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
 
// 需要设置 HttpOnly 防止 XSS
// 但有 CSRF 风险

四、Node.js 实现 #

4.1 安装依赖 #

# 安装 jsonwebtoken
npm install jsonwebtoken
 
# 安装 express(Web 框架)
npm install express
 
# 安装 bcryptjs(密码加密)
npm install bcryptjs

4.2 基础示例:生成和验证 JWT #

// jwt-demo.js
const jwt = require('jsonwebtoken');
 
// 密钥(实际项目中应该放在环境变量)
const SECRET_KEY = 'your-secret-key';
 
// ==================== 生成 JWT ====================
 
// 用户数据(Payload)
const user = {
  userId: '12345',
  username: 'johndoe',
  role: 'admin'
};
 
// 生成 Token
const token = jwt.sign(
  user,                              // Payload
  SECRET_KEY,                         // 密钥
  { 
    expiresIn: '2h',                  // 过期时间:2小时
    issuer: 'myapp.com',              // 签发者
    subject: 'user-auth'              // 主题
  }
);
 
console.log('生成的 Token:', token);
 
// ==================== 验证 JWT ====================
 
try {
  // 验证 Token
  const decoded = jwt.verify(token, SECRET_KEY);
  console.log('解析结果:', decoded);
  // 输出:
  // {
  //   userId: '12345',
  //   username: 'johndoe',
  //   role: 'admin',
  //   iat: 1640000000,  // 签发时间
  //   exp: 1640007200,  // 过期时间
  //   iss: 'myapp.com',
  //   sub: 'user-auth'
  // }
} catch (error) {
  console.error('Token 验证失败:', error.message);
}
 
// ==================== 解码 JWT(不验证) ====================
 
// 只解码,不验证签名(用于查看内容)
const decodedWithoutVerify = jwt.decode(token);
console.log('解码结果(不验证):', decodedWithoutVerify);
 
// ==================== 过期时间格式 ====================
 
// expiresIn 支持多种格式
jwt.sign(user, SECRET_KEY, { expiresIn: '2h' });      // 2小时
jwt.sign(user, SECRET_KEY, { expiresIn: '2d' });      // 2天
jwt.sign(user, SECRET_KEY, { expiresIn: '7d' });      // 7天
jwt.sign(user, SECRET_KEY, { expiresIn: 3600 });      // 3600秒(1小时)
jwt.sign(user, SECRET_KEY, { expiresIn: '1y' });      // 1年(不推荐)

4.3 Express 完整示例:登录认证 #

// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
 
const app = express();
app.use(express.json());
 
// 配置
const SECRET_KEY = 'your-secret-key-should-be-in-env';
const JWT_EXPIRES_IN = '2h';
const REFRESH_TOKEN_EXPIRES_IN = '7d';
 
// 模拟数据库
const users = [
  {
    id: '1',
    username: 'admin',
    // 密码是 "123456" 的 hash
    password: '$2a$10$YourHashedPasswordHere...',
    role: 'admin'
  },
  {
    id: '2',
    username: 'user',
    password: '$2a$10$YourHashedPasswordHere...',
    role: 'user'
  }
];
 
// 存储刷新令牌(实际应该用 Redis)
const refreshTokens = new Set();
 
// ==================== 工具函数 ====================
 
// 生成 Access Token
function generateAccessToken(user) {
  return jwt.sign(
    { 
      userId: user.id, 
      username: user.username, 
      role: user.role 
    },
    SECRET_KEY,
    { expiresIn: JWT_EXPIRES_IN }
  );
}
 
// 生成 Refresh Token
function generateRefreshToken(user) {
  const token = jwt.sign(
    { userId: user.id },
    SECRET_KEY,
    { expiresIn: REFRESH_TOKEN_EXPIRES_IN }
  );
  refreshTokens.add(token);
  return token;
}
 
// 验证 JWT 中间件
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
  
  if (!token) {
    return res.status(401).json({ 
      code: 401, 
      message: '缺少 Token' 
    });
  }
  
  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) {
      if (err.name === 'TokenExpiredError') {
        return res.status(401).json({ 
          code: 401, 
          message: 'Token 已过期' 
        });
      }
      if (err.name === 'JsonWebTokenError') {
        return res.status(403).json({ 
          code: 403, 
          message: 'Token 无效' 
        });
      }
      return res.status(403).json({ 
        code: 403, 
        message: 'Token 验证失败' 
      });
    }
    
    req.user = user;
    next();
  });
}
 
// 权限检查中间件
function checkRole(...roles) {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ 
        code: 403, 
        message: '权限不足' 
      });
    }
    next();
  };
}
 
// ==================== 路由 ====================
 
// 用户注册
app.post('/api/register', async (req, res) => {
  const { username, password } = req.body;
  
  // 检查用户是否存在
  if (users.find(u => u.username === username)) {
    return res.status(400).json({ 
      code: 400, 
      message: '用户名已存在' 
    });
  }
  
  // 密码加密
  const hashedPassword = await bcrypt.hash(password, 10);
  
  // 创建用户
  const newUser = {
    id: String(users.length + 1),
    username,
    password: hashedPassword,
    role: 'user'
  };
  
  users.push(newUser);
  
  res.json({ 
    code: 200, 
    message: '注册成功',
    data: { userId: newUser.id }
  });
});
 
// 用户登录
app.post('/api/login', async (req, res) => {
  const { username, password } = req.body;
  
  // 查找用户
  const user = users.find(u => u.username === username);
  if (!user) {
    return res.status(401).json({ 
      code: 401, 
      message: '用户名或密码错误' 
    });
  }
  
  // 验证密码
  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    return res.status(401).json({ 
      code: 401, 
      message: '用户名或密码错误' 
    });
  }
  
  // 生成 Token
  const accessToken = generateAccessToken(user);
  const refreshToken = generateRefreshToken(user);
  
  res.json({
    code: 200,
    message: '登录成功',
    data: {
      accessToken,
      refreshToken,
      expiresIn: JWT_EXPIRES_IN,
      user: {
        id: user.id,
        username: user.username,
        role: user.role
      }
    }
  });
});
 
// 刷新 Token
app.post('/api/refresh', (req, res) => {
  const { refreshToken } = req.body;
  
  if (!refreshToken) {
    return res.status(400).json({ 
      code: 400, 
      message: '缺少 refreshToken' 
    });
  }
  
  // 检查 refreshToken 是否有效
  if (!refreshTokens.has(refreshToken)) {
    return res.status(403).json({ 
      code: 403, 
      message: '无效的 refreshToken' 
    });
  }
  
  jwt.verify(refreshToken, SECRET_KEY, (err, decoded) => {
    if (err) {
      refreshTokens.delete(refreshToken);
      return res.status(403).json({ 
        code: 403, 
        message: 'refreshToken 无效或已过期' 
      });
    }
    
    // 查找用户
    const user = users.find(u => u.id === decoded.userId);
    if (!user) {
      return res.status(404).json({ 
        code: 404, 
        message: '用户不存在' 
      });
    }
    
    // 生成新的 Token
    const newAccessToken = generateAccessToken(user);
    const newRefreshToken = generateRefreshToken(user);
    
    // 删除旧的 refreshToken
    refreshTokens.delete(refreshToken);
    
    res.json({
      code: 200,
      message: '刷新成功',
      data: {
        accessToken: newAccessToken,
        refreshToken: newRefreshToken,
        expiresIn: JWT_EXPIRES_IN
      }
    });
  });
});
 
// 退出登录
app.post('/api/logout', (req, res) => {
  const { refreshToken } = req.body;
  
  if (refreshToken) {
    refreshTokens.delete(refreshToken);
  }
  
  res.json({ 
    code: 200, 
    message: '退出成功' 
  });
});
 
// 获取用户信息(需要认证)
app.get('/api/user', authenticateToken, (req, res) => {
  res.json({
    code: 200,
    data: {
      userId: req.user.userId,
      username: req.user.username,
      role: req.user.role
    }
  });
});
 
// 管理员接口(需要 admin 权限)
app.get('/api/admin/users', authenticateToken, checkRole('admin'), (req, res) => {
  res.json({
    code: 200,
    data: users.map(u => ({
      id: u.id,
      username: u.username,
      role: u.role
    }))
  });
});
 
// 启动服务器
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

4.4 前端使用示例 #

// api.js
const API_BASE_URL = 'http://localhost:3000';
 
// 存储 Token
function saveTokens(accessToken, refreshToken) {
  localStorage.setItem('accessToken', accessToken);
  localStorage.setItem('refreshToken', refreshToken);
}
 
// 获取 Token
function getAccessToken() {
  return localStorage.getItem('accessToken');
}
 
// 清除 Token
function clearTokens() {
  localStorage.removeItem('accessToken');
  localStorage.removeItem('refreshToken');
}
 
// 封装请求
async function request(url, options = {}) {
  const accessToken = getAccessToken();
  
  const headers = {
    'Content-Type': 'application/json',
    ...options.headers,
  };
  
  if (accessToken) {
    headers['Authorization'] = `Bearer ${accessToken}`;
  }
  
  const response = await fetch(`${API_BASE_URL}${url}`, {
    ...options,
    headers,
  });
  
  const data = await response.json();
  
  // Token 过期,尝试刷新
  if (data.code === 401 && data.message === 'Token 已过期') {
    const newToken = await refreshToken();
    if (newToken) {
      // 重试请求
      headers['Authorization'] = `Bearer ${newToken}`;
      const retryResponse = await fetch(`${API_BASE_URL}${url}`, {
        ...options,
        headers,
      });
      return retryResponse.json();
    } else {
      // 刷新失败,跳转登录
      clearTokens();
      window.location.href = '/login';
    }
  }
  
  return data;
}
 
// 刷新 Token
async function refreshToken() {
  const refreshToken = localStorage.getItem('refreshToken');
  if (!refreshToken) return null;
  
  try {
    const response = await fetch(`${API_BASE_URL}/api/refresh`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refreshToken }),
    });
    
    const data = await response.json();
    
    if (data.code === 200) {
      saveTokens(data.data.accessToken, data.data.refreshToken);
      return data.data.accessToken;
    }
    
    return null;
  } catch (error) {
    console.error('刷新 Token 失败:', error);
    return null;
  }
}
 
// 登录
async function login(username, password) {
  const data = await request('/api/login', {
    method: 'POST',
    body: JSON.stringify({ username, password }),
  });
  
  if (data.code === 200) {
    saveTokens(data.data.accessToken, data.data.refreshToken);
  }
  
  return data;
}
 
// 获取用户信息
async function getUser() {
  return request('/api/user');
}
 
// 使用示例
login('admin', '123456').then(() => {
  return getUser();
}).then(user => {
  console.log('用户信息:', user);
});

五、Java 实现 #

5.1 添加依赖 #

Maven:

<!-- pom.xml -->
<dependencies>
  <!-- JWT 库:jjwt -->
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
  </dependency>
  
  <!-- Spring Boot Web -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  
  <!-- Spring Security(可选) -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
</dependencies>

Gradle:

// build.gradle
dependencies {
  implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
  runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
  runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
  
  implementation 'org.springframework.boot:spring-boot-starter-web'
}

5.2 JWT 工具类 #

// JwtUtil.java
package com.example.demo.util;
 
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
 
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
 
@Component
public class JwtUtil {
    
    @Value("${jwt.secret:your-secret-key-must-be-at-least-256-bits-long}")
    private String secret;
    
    @Value("${jwt.expiration:7200000}") // 默认 2 小时
    private Long expiration;
    
    @Value("${jwt.refresh-expiration:604800000}") // 默认 7 天
    private Long refreshExpiration;
    
    // 生成密钥
    private SecretKey getSecretKey() {
        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }
    
    /**
     * 生成 Access Token
     */
    public String generateToken(String userId, String username, String role) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        claims.put("username", username);
        claims.put("role", role);
        
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userId)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(getSecretKey(), SignatureAlgorithm.HS256)
                .compact();
    }
    
    /**
     * 生成 Refresh Token
     */
    public String generateRefreshToken(String userId) {
        return Jwts.builder()
                .setSubject(userId)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + refreshExpiration))
                .signWith(getSecretKey(), SignatureAlgorithm.HS256)
                .compact();
    }
    
    /**
     * 解析 Token
     */
    public Claims parseToken(String token) {
        try {
            return Jwts.parserBuilder()
                    .setSigningKey(getSecretKey())
                    .build()
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            return null;
        }
    }
    
    /**
     * 验证 Token 是否有效
     */
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder()
                    .setSigningKey(getSecretKey())
                    .build()
                    .parseClaimsJws(token);
            return true;
        } catch (ExpiredJwtException e) {
            System.out.println("Token 已过期: " + e.getMessage());
        } catch (UnsupportedJwtException e) {
            System.out.println("不支持的 Token: " + e.getMessage());
        } catch (MalformedJwtException e) {
            System.out.println("Token 格式错误: " + e.getMessage());
        } catch (SignatureException e) {
            System.out.println("签名验证失败: " + e.getMessage());
        } catch (IllegalArgumentException e) {
            System.out.println("Token 为空: " + e.getMessage());
        }
        return false;
    }
    
    /**
     * 检查 Token 是否过期
     */
    public boolean isTokenExpired(String token) {
        Claims claims = parseToken(token);
        if (claims == null) return true;
        return claims.getExpiration().before(new Date());
    }
    
    /**
     * 从 Token 获取用户 ID
     */
    public String getUserIdFromToken(String token) {
        Claims claims = parseToken(token);
        return claims != null ? claims.getSubject() : null;
    }
    
    /**
     * 从 Token 获取用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = parseToken(token);
        return claims != null ? (String) claims.get("username") : null;
    }
    
    /**
     * 从 Token 获取角色
     */
    public String getRoleFromToken(String token) {
        Claims claims = parseToken(token);
        return claims != null ? (String) claims.get("role") : null;
    }
}

5.3 Spring Boot 完整示例 #

// User.java
package com.example.demo.entity;
 
public class User {
    private String id;
    private String username;
    private String password;
    private String role;
    
    // getter and setter
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
    public String getRole() { return role; }
    public void setRole(String role) { this.role = role; }
}
 
// LoginRequest.java
package com.example.demo.dto;
 
public class LoginRequest {
    private String username;
    private String password;
    
    // getter and setter
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}
 
// LoginResponse.java
package com.example.demo.dto;
 
public class LoginResponse {
    private String accessToken;
    private String refreshToken;
    private Long expiresIn;
    private UserInfo user;
    
    // getter and setter
    public String getAccessToken() { return accessToken; }
    public void setAccessToken(String accessToken) { this.accessToken = accessToken; }
    public String getRefreshToken() { return refreshToken; }
    public void setRefreshToken(String refreshToken) { this.refreshToken = refreshToken; }
    public Long getExpiresIn() { return expiresIn; }
    public void setExpiresIn(Long expiresIn) { this.expiresIn = expiresIn; }
    public UserInfo getUser() { return user; }
    public void setUser(UserInfo user) { this.user = user; }
    
    public static class UserInfo {
        private String id;
        private String username;
        private String role;
        
        public UserInfo(String id, String username, String role) {
            this.id = id;
            this.username = username;
            this.role = role;
        }
        
        // getter
        public String getId() { return id; }
        public String getUsername() { return username; }
        public String getRole() { return role; }
    }
}
 
// AuthController.java
package com.example.demo.controller;
 
import com.example.demo.dto.LoginRequest;
import com.example.demo.dto.LoginResponse;
import com.example.demo.entity.User;
import com.example.demo.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
 
import java.util.HashMap;
import java.util.Map;
 
@RestController
@RequestMapping("/api")
public class AuthController {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    // 模拟数据库
    private Map<String, User> userDatabase = new HashMap<>();
    
    // 模拟 refreshToken 存储
    private Map<String, String> refreshTokenStore = new HashMap<>();
    
    // 初始化测试用户
    public AuthController() {
        User admin = new User();
        admin.setId("1");
        admin.setUsername("admin");
        admin.setPassword("admin123"); // 实际应该加密
        admin.setRole("admin");
        userDatabase.put("admin", admin);
        
        User user = new User();
        user.setId("2");
        user.setUsername("user");
        user.setPassword("user123");
        user.setRole("user");
        userDatabase.put("user", user);
    }
    
    /**
     * 用户登录
     */
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest request) {
        User user = userDatabase.get(request.getUsername());
        
        if (user == null || !user.getPassword().equals(request.getPassword())) {
            return ResponseEntity.status(401).body(Map.of(
                "code", 401,
                "message", "用户名或密码错误"
            ));
        }
        
        // 生成 Token
        String accessToken = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole());
        String refreshToken = jwtUtil.generateRefreshToken(user.getId());
        
        // 存储 refreshToken
        refreshTokenStore.put(user.getId(), refreshToken);
        
        // 构建响应
        LoginResponse response = new LoginResponse();
        response.setAccessToken(accessToken);
        response.setRefreshToken(refreshToken);
        response.setExpiresIn(7200000L);
        response.setUser(new LoginResponse.UserInfo(user.getId(), user.getUsername(), user.getRole()));
        
        return ResponseEntity.ok(Map.of(
            "code", 200,
            "message", "登录成功",
            "data", response
        ));
    }
    
    /**
     * 刷新 Token
     */
    @PostMapping("/refresh")
    public ResponseEntity<?> refresh(@RequestBody Map<String, String> request) {
        String refreshToken = request.get("refreshToken");
        
        if (refreshToken == null || !jwtUtil.validateToken(refreshToken)) {
            return ResponseEntity.status(403).body(Map.of(
                "code", 403,
                "message", "无效的 refreshToken"
            ));
        }
        
        String userId = jwtUtil.getUserIdFromToken(refreshToken);
        
        // 验证 refreshToken 是否在存储中
        if (!refreshToken.equals(refreshTokenStore.get(userId))) {
            return ResponseEntity.status(403).body(Map.of(
                "code", 403,
                "message", "refreshToken 已失效"
            ));
        }
        
        // 查找用户
        User user = null;
        for (User u : userDatabase.values()) {
            if (u.getId().equals(userId)) {
                user = u;
                break;
            }
        }
        
        if (user == null) {
            return ResponseEntity.status(404).body(Map.of(
                "code", 404,
                "message", "用户不存在"
            ));
        }
        
        // 生成新 Token
        String newAccessToken = jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole());
        String newRefreshToken = jwtUtil.generateRefreshToken(user.getId());
        
        // 更新 refreshToken
        refreshTokenStore.put(user.getId(), newRefreshToken);
        
        return ResponseEntity.ok(Map.of(
            "code", 200,
            "message", "刷新成功",
            "data", Map.of(
                "accessToken", newAccessToken,
                "refreshToken", newRefreshToken,
                "expiresIn", 7200000L
            )
        ));
    }
    
    /**
     * 获取用户信息
     */
    @GetMapping("/user")
    public ResponseEntity<?> getUserInfo(@RequestHeader("Authorization") String authorization) {
        // 提取 Token
        String token = authorization.replace("Bearer ", "");
        
        if (!jwtUtil.validateToken(token)) {
            return ResponseEntity.status(401).body(Map.of(
                "code", 401,
                "message", "Token 无效或已过期"
            ));
        }
        
        String userId = jwtUtil.getUserIdFromToken(token);
        String username = jwtUtil.getUsernameFromToken(token);
        String role = jwtUtil.getRoleFromToken(token);
        
        return ResponseEntity.ok(Map.of(
            "code", 200,
            "data", Map.of(
                "userId", userId,
                "username", username,
                "role", role
            )
        ));
    }
    
    /**
     * 退出登录
     */
    @PostMapping("/logout")
    public ResponseEntity<?> logout(@RequestBody Map<String, String> request) {
        String refreshToken = request.get("refreshToken");
        
        if (refreshToken != null) {
            String userId = jwtUtil.getUserIdFromToken(refreshToken);
            refreshTokenStore.remove(userId);
        }
        
        return ResponseEntity.ok(Map.of(
            "code", 200,
            "message", "退出成功"
        ));
    }
}
 
// DemoApplication.java
package com.example.demo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

5.4 application.yml 配置 #

# application.yml
server:
  port: 8080
 
jwt:
  secret: your-secret-key-must-be-at-least-256-bits-long-for-hs256
  expiration: 7200000      # 2 小时(毫秒)
  refresh-expiration: 604800000  # 7 天(毫秒)
 
spring:
  application:
    name: jwt-demo

六、实战场景 #

场景 1:单点登录(SSO) #

// SSO 服务端
// 登录成功后,生成 Token,重定向到各子系统
 
// sso-server.js
app.post('/sso/login', async (req, res) => {
  const { username, password, redirectUrl } = req.body;
  
  // 验证用户
  const user = await authenticateUser(username, password);
  if (!user) {
    return res.status(401).json({ message: '认证失败' });
  }
  
  // 生成 Token
  const token = jwt.sign(
    { userId: user.id, username: user.username },
    SSO_SECRET,
    { expiresIn: '5m' }  // 短有效期,仅用于一次跳转
  );
  
  // 重定向到子系统,携带 Token
  res.redirect(`${redirectUrl}?token=${token}`);
});
 
// 子系统接收 Token
// subsystem.js
app.get('/sso/callback', async (req, res) => {
  const { token } = req.query;
  
  // 验证 SSO Token
  try {
    const decoded = jwt.verify(token, SSO_SECRET);
    
    // 生成本系统 Token
    const localToken = jwt.sign(
      { userId: decoded.userId },
      LOCAL_SECRET,
      { expiresIn: '2h' }
    );
    
    // 设置本地 Token,完成登录
    res.cookie('token', localToken, { httpOnly: true });
    res.redirect('/dashboard');
  } catch (error) {
    res.status(401).send('Token 无效');
  }
});

场景 2:API 授权 #

// 第三方应用访问 API
// api-server.js
 
// 注册应用,获取 appId 和 appSecret
const apps = {
  'app-001': {
    id: 'app-001',
    secret: 'secret-key-001',
    permissions: ['read:user', 'read:order']
  }
};
 
// 获取 Access Token
app.post('/oauth/token', (req, res) => {
  const { appId, appSecret } = req.body;
  
  const app = apps[appId];
  if (!app || app.secret !== appSecret) {
    return res.status(401).json({ message: '应用认证失败' });
  }
  
  // 生成 Token
  const token = jwt.sign(
    { 
      appId: app.id, 
      permissions: app.permissions 
    },
    API_SECRET,
    { expiresIn: '1h' }
  );
  
  res.json({
    access_token: token,
    token_type: 'Bearer',
    expires_in: 3600
  });
});
 
// 权限检查中间件
function checkPermission(permission) {
  return (req, res, next) => {
    const token = req.headers.authorization?.replace('Bearer ', '');
    
    try {
      const decoded = jwt.verify(token, API_SECRET);
      
      if (!decoded.permissions.includes(permission)) {
        return res.status(403).json({ message: '权限不足' });
      }
      
      req.app = decoded;
      next();
    } catch (error) {
      res.status(401).json({ message: 'Token 无效' });
    }
  };
}
 
// API 路由
app.get('/api/users', checkPermission('read:user'), (req, res) => {
  // 返回用户数据
  res.json({ users: [...] });
});
 
app.get('/api/orders', checkPermission('read:order'), (req, res) => {
  // 返回订单数据
  res.json({ orders: [...] });
});

场景 3:微服务间通信 #

// 微服务间调用时传递用户信息
 
// 服务 A 生成 Token
@RestController
public class ServiceAController {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @GetMapping("/call-service-b")
    public String callServiceB(HttpServletRequest request) {
        // 从请求中获取用户信息
        String userToken = request.getHeader("Authorization");
        Claims claims = jwtUtil.parseToken(userToken.replace("Bearer ", ""));
        
        // 生成服务间 Token
        String serviceToken = jwtUtil.generateToken(
            claims.get("userId", String.class),
            claims.get("username", String.class),
            claims.get("role", String.class)
        );
        
        // 调用服务 B
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + serviceToken);
        
        HttpEntity<String> entity = new HttpEntity<>(headers);
        ResponseEntity<String> response = restTemplate.exchange(
            "http://service-b:8081/api/data",
            HttpMethod.GET,
            entity,
            String.class
        );
        
        return response.getBody();
    }
}
 
// 服务 B 验证 Token
@RestController
public class ServiceBController {
    
    @Autowired
    private JwtUtil jwtUtil;
    
    @GetMapping("/api/data")
    public Map<String, Object> getData(@RequestHeader("Authorization") String authorization) {
        String token = authorization.replace("Bearer ", "");
        Claims claims = jwtUtil.parseToken(token);
        
        return Map.of(
            "userId", claims.get("userId"),
            "username", claims.get("username"),
            "data", "敏感数据"
        );
    }
}

场景 4:WebSocket 认证 #

// WebSocket 连接时验证 JWT
 
const jwt = require('jsonwebtoken');
const WebSocket = require('ws');
 
const wss = new WebSocket.Server({ port: 8080 });
 
// 连接时验证 Token
wss.on('connection', (ws, req) => {
  // 从 URL 参数获取 Token
  const url = new URL(req.url, 'http://localhost');
  const token = url.searchParams.get('token');
  
  if (!token) {
    ws.send(JSON.stringify({ error: '缺少 Token' }));
    ws.close();
    return;
  }
  
  try {
    // 验证 Token
    const decoded = jwt.verify(token, SECRET_KEY);
    
    // 保存用户信息到 ws 对象
    ws.userId = decoded.userId;
    ws.username = decoded.username;
    
    ws.send(JSON.stringify({ message: '连接成功' }));
    
    // 处理消息
    ws.on('message', (message) => {
      console.log(`收到来自 ${ws.username} 的消息: ${message}`);
      // 广播给所有连接
      wss.clients.forEach(client => {
        if (client.readyState === WebSocket.OPEN) {
          client.send(JSON.stringify({
            from: ws.username,
            message: message.toString()
          }));
        }
      });
    });
  } catch (error) {
    ws.send(JSON.stringify({ error: 'Token 无效' }));
    ws.close();
  }
});
 
// 客户端连接
const ws = new WebSocket('ws://localhost:8080?token=xxx.yyy.zzz');

七、安全最佳实践 #

7.1 密钥管理 #

// ❌ 错误:硬编码密钥
const SECRET = '123456';
 
// ✅ 正确:从环境变量读取
const SECRET = process.env.JWT_SECRET;
 
// ✅ 正确:密钥长度足够(HS256 至少 256 位)
const SECRET = 'your-secret-key-must-be-at-least-256-bits-long';
 
// ✅ 正确:使用非对称加密(更安全)
// 私钥签名,公钥验证
const privateKey = fs.readFileSync('private.key');
const publicKey = fs.readFileSync('public.key');
 
// 生成 Token
const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });
 
// 验证 Token
const decoded = jwt.verify(token, publicKey);

7.2 Token 有效期 #

// ✅ 合理的过期时间
const ACCESS_TOKEN_EXPIRES = '15m';   // Access Token: 15 分钟
const REFRESH_TOKEN_EXPIRES = '7d';   // Refresh Token: 7 天
 
// ❌ 错误:永不过期
const token = jwt.sign(payload, secret, { expiresIn: '100y' });
 
// ✅ 正确:根据场景设置
// 高安全场景(金融、支付)
const HIGH_SECURITY_EXPIRES = '5m';
 
// 普通场景(社交、内容)
const NORMAL_EXPIRES = '2h';
 
// 低安全场景(内容浏览)
const LOW_SECURITY_EXPIRES = '24h';

7.3 Token 存储 #

// ❌ 错误:存储在 URL 中
window.location.href = '/dashboard?token=' + token;
 
// ❌ 错误:存储在 localStorage(有 XSS 风险)
localStorage.setItem('token', token);
 
// ✅ 较好:存储在 sessionStorage(关闭标签页即清除)
sessionStorage.setItem('token', token);
 
// ✅ 最好:存储在 HttpOnly Cookie
res.cookie('token', token, {
  httpOnly: true,    // 防止 JavaScript 访问(防 XSS)
  secure: true,      // 只在 HTTPS 下发送
  sameSite: 'strict', // 防止 CSRF
  maxAge: 7200000    // 2 小时
});
 
// 前端无需手动存储,浏览器自动携带
fetch('/api/user', {
  credentials: 'include'  // 携带 Cookie
});

7.4 Token 注销 #

// JWT 无状态,无法主动失效
// 解决方案:维护黑名单或白名单
 
// 方案 1:黑名单(Redis)
const redis = require('redis');
const client = redis.createClient();
 
// 登出时加入黑名单
app.post('/logout', (req, res) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  // 解析 Token 获取过期时间
  const decoded = jwt.decode(token);
  const ttl = decoded.exp - Math.floor(Date.now() / 1000);
  
  // 存入 Redis,过期时间与 Token 相同
  client.setex(`blacklist:${token}`, ttl, '1');
  
  res.json({ message: '登出成功' });
});
 
// 验证时检查黑名单
function verifyToken(token) {
  // 先检查黑名单
  const isBlacklisted = await client.get(`blacklist:${token}`);
  if (isBlacklisted) {
    throw new Error('Token 已失效');
  }
  
  // 再验证签名
  return jwt.verify(token, SECRET);
}
 
// 方案 2:白名单(Redis)
// 每次登录生成唯一 jti,存入 Redis
// 验证时检查 jti 是否在白名单

7.5 防止攻击 #

// 1. 防止 XSS
// - 不要在 localStorage 存 Token
// - 使用 HttpOnly Cookie
// - 对用户输入进行转义
 
// 2. 防止 CSRF
// - 使用 SameSite Cookie
// - 验证 CSRF Token
// - 使用 Authorization Header 而不是 Cookie
 
// 3. 防止 Token 泄露
// - 使用 HTTPS
// - 设置合理的过期时间
// - 敏感操作需要二次验证
 
// 4. 防止重放攻击
// 使用 jti(JWT ID)和 nonce
const token = jwt.sign(
  {
    userId: '123',
    jti: crypto.randomUUID()  // 唯一标识
  },
  SECRET,
  { expiresIn: '15m' }
);
 
// 服务端记录已使用的 jti
const usedJti = new Set();
 
function verifyWithReplayProtection(token) {
  const decoded = jwt.verify(token, SECRET);
  
  if (usedJti.has(decoded.jti)) {
    throw new Error('Token 已使用');
  }
  
  usedJti.add(decoded.jti);
  
  // 设置过期时间
  setTimeout(() => {
    usedJti.delete(decoded.jti);
  }, decoded.exp * 1000 - Date.now());
  
  return decoded;
}

八、JWT vs Session #

8.1 对比表 #

特性 JWT Session
存储位置 客户端 服务器
状态 无状态 有状态
扩展性 容易水平扩展 需要 Session 共享
跨域 友好 需要特殊处理
移动端 友好 Cookie 不友好
安全性 需防 XSS 需防 CSRF
注销 困难(需要黑名单) 简单(删除 Session)
性能 每次都要验证签名 查询 Session(内存快)
数据量 Token 较大 Session ID 很小

8.2 选择建议 #

使用 JWT 的场景:

  • 前后端分离架构
  • 移动端 App
  • 微服务架构
  • 单点登录(SSO)
  • 第三方 API 授权

使用 Session 的场景:

  • 传统单体应用
  • 需要即时注销
  • 敏感操作频繁
  • 不需要跨域
  • 服务器资源充足

8.3 混合方案 #

// 登录时同时使用 JWT 和 Session
app.post('/login', (req, res) => {
  // 1. 创建 Session(用于敏感操作)
  req.session.userId = user.id;
  
  // 2. 生成 JWT(用于 API 认证)
  const token = jwt.sign({ userId: user.id }, SECRET);
  
  // 3. 返回 Token
  res.json({
    token,
    message: '登录成功'
  });
});
 
// 普通操作:用 JWT
app.get('/api/data', verifyJWT, (req, res) => {
  // ...
});
 
// 敏感操作:用 Session
app.post('/api/change-password', (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ message: '请先登录' });
  }
  // ...
});

九、常见问题 #

Q1:JWT 被盗用怎么办? #

防范措施:

  1. 短有效期 - Access Token 15-30 分钟
  2. HTTPS - 防止中间人攻击
  3. HttpOnly Cookie - 防止 XSS
  4. 刷新机制 - Refresh Token 轮换
  5. IP 绑定 - Token 与 IP 绑定(可选)
  6. 设备绑定 - Token 与设备绑定(可选)
// IP 绑定示例
const token = jwt.sign(
  {
    userId: user.id,
    ip: req.ip  // 记录登录 IP
  },
  SECRET
);
 
// 验证时检查 IP
function verifyToken(token, currentIp) {
  const decoded = jwt.verify(token, SECRET);
  
  if (decoded.ip !== currentIp) {
    throw new Error('IP 地址已变化');
  }
  
  return decoded;
}

Q2:JWT 过期后如何处理? #

// 前端处理
async function request(url, options) {
  const response = await fetch(url, options);
  
  if (response.status === 401) {
    const data = await response.json();
    
    if (data.message === 'Token 已过期') {
      // 尝试刷新
      const newToken = await refreshToken();
      
      if (newToken) {
        // 重试请求
        options.headers.Authorization = `Bearer ${newToken}`;
        return fetch(url, options);
      } else {
        // 跳转登录
        window.location.href = '/login';
      }
    }
  }
  
  return response;
}

Q3:JWT 可以存储敏感信息吗? #

不可以!

// ❌ 危险:存储密码
const token = jwt.sign({ password: '123456' }, SECRET);
 
// JWT 只是 Base64 编码,不是加密!
// 任何人都可以解码看到内容
 
// 解码 Payload(不需要密钥)
const payload = JSON.parse(atob(token.split('.')[1]));
console.log(payload);  // { password: '123456' }

Q4:如何实现"记住我"功能? #

// 登录时根据选项设置不同的过期时间
app.post('/login', (req, res) => {
  const { username, password, rememberMe } = req.body;
  
  // ...
  
  // 根据"记住我"设置不同的过期时间
  const expiresIn = rememberMe ? '30d' : '2h';
  
  const token = jwt.sign(
    { userId: user.id },
    SECRET,
    { expiresIn }
  );
  
  res.json({ token, expiresIn });
});

Q5:JWT 能用于移动端吗? #

可以,这是 JWT 的优势之一。

// 移动端存储
// iOS:Keychain
// Android:SharedPreferences(加密)或 Keystore
 
// 请求示例(iOS Swift)
var request = URLRequest(url: url)
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

Q6:如何处理并发 Token 刷新? #

// 问题:多个请求同时发现 Token 过期,都去刷新
// 解决:加锁,只刷新一次
 
let isRefreshing = false;
let refreshSubscribers = [];
 
async function refreshToken() {
  if (isRefreshing) {
    // 等待刷新完成
    return new Promise(resolve => {
      refreshSubscribers.push(resolve);
    });
  }
  
  isRefreshing = true;
  
  try {
    const response = await fetch('/api/refresh', {
      method: 'POST',
      body: JSON.stringify({ refreshToken })
    });
    
    const data = await response.json();
    const newToken = data.data.accessToken;
    
    // 通知所有等待的请求
    refreshSubscribers.forEach(callback => callback(newToken));
    refreshSubscribers = [];
    
    return newToken;
  } finally {
    isRefreshing = false;
  }
}

Q7:JWT 的 Payload 大小有限制吗? #

没有严格限制,但建议小于 4KB。

// 问题:Token 太大
const bigData = { /* ... 10KB 数据 ... */ };
const token = jwt.sign(bigData, SECRET);
// 结果:Token 字符串很长,HTTP 请求体积大
 
// 建议:只存必要的标识
const token = jwt.sign({ userId: '123' }, SECRET);
// 其他数据从数据库查询

十、总结速记 #

JWT 三部分 #

Header.Payload.Signature
 
Header: 算法和类型
Payload: 用户数据(Base64,不加密)
Signature: 防伪签名

Node.js 快速使用 #

const jwt = require('jsonwebtoken');
 
// 生成
const token = jwt.sign({ userId: '123' }, SECRET, { expiresIn: '2h' });
 
// 验证
const decoded = jwt.verify(token, SECRET);
 
// 解码(不验证)
const payload = jwt.decode(token);

Java 快速使用 #

// 生成
String token = Jwts.builder()
    .setSubject("123")
    .setExpiration(new Date(System.currentTimeMillis() + 7200000))
    .signWith(Keys.hmacShaKeyFor(SECRET.getBytes()), SignatureAlgorithm.HS256)
    .compact();
 
// 验证
Claims claims = Jwts.parserBuilder()
    .setSigningKey(Keys.hmacShaKeyFor(SECRET.getBytes()))
    .build()
    .parseClaimsJws(token)
    .getBody();

安全要点 #

1. 密钥要长:至少 256 位
2. 有效期短:Access Token 15-30 分钟
3. HTTPS:防止传输泄露
4. 不存敏感:Payload 不加密
5. 存储:HttpOnly Cookie 优于 localStorage
6. 刷新:使用 Refresh Token

JWT vs Session 选择 #

JWT:
- 分布式、微服务
- 移动端
- SSO
- API 授权
 
Session:
- 单体应用
- 敏感操作
- 即时注销

典型流程 #

1. 登录 → 返回 JWT
2. 存储 Token(Cookie/localStorage)
3. 请求携带 Token(Authorization: Bearer xxx)
4. 服务器验证签名 + 检查过期
5. 过期 → 刷新 Token

最后更新:2026-03-29