JWT 双 Token 认证机制:从实现到原理的深度解析

508 阅读5分钟

在现代 Web 应用中,身份认证是保障系统安全的核心环节。随着前后端分离架构的普及,JWT(JSON Web Token)凭借其无状态特性成为主流认证方案。而双 Token 机制则是在 JWT 基础上发展出的更安全、更灵活的认证模式。本文将结合实际代码实现,详细解析 JWT 双 Token 机制的工作原理、实现细节及安全考量。

什么是 JWT 双 Token 机制?

JWT 双 Token 机制通过同时使用两种令牌来实现身份认证:

  • Access Token(访问令牌) :短期有效,主要用于 API 访问时的身份验证
  • Refresh Token(刷新令牌) :长期有效,仅用于获取新的 Access Token

这种机制解决了单一令牌在安全性和用户体验之间的矛盾 —— 短期有效的 Access Token 降低了令牌泄露的风险,而长期有效的 Refresh Token 则避免了用户频繁登录的麻烦

核心实现代码解析

1. 登录流程(Login API)

登录是获取双 Token 的入口,对应代码位于app/api/auth/login/route.ts:

// 验证用户凭据
const user = await prisma.user.findUnique({ where: { email } });
if(!user) {
  return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 });
}

// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password);
if(!isPasswordValid) {
  return NextResponse.json({ error: 'Invalid password' }, { status: 401 });
}

// 生成双Token
const { accessToken, refreshToken } = await createTokens(user.id);

// 存储Refresh Token到数据库
await prisma.user.update({
  where: { id: user.id },
  data: { refreshToken }
});

// 设置认证Cookie
setAuthCookies(accessToken, refreshToken);

登录流程详解

  1. 用户身份验证:验证邮箱格式、密码强度及正确性
  2. Token 生成:调用createTokens()生成一对令牌
  3. 安全存储:将 Refresh Token 存入数据库,便于后续验证
  4. Cookie 设置:通过setAuthCookies()方法将令牌存入 HTTP-only Cookie

2. Token 刷新流程(Refresh API)

当 Access Token 过期时,需要通过 Refresh Token 获取新的令牌对,对应代码位于app/api/auth/refresh/route.ts:

// 获取Cookie中的refresh token
const refreshToken = request.cookies.get("refresh_token")?.value;

// 验证refresh token存在性
if(!refreshToken) {
  return NextResponse.redirect(new URL('/login', request.url));
}

// 第一步:JWT签名验证
const refreshPayload = await verifyToken(refreshToken);
if(!refreshPayload || !refreshPayload.userId) {
  return NextResponse.redirect(new URL('/login', request.url));
}

// 第二步:数据库比对验证
const user = await prisma.user.findUnique({
  where: { id: refreshPayload.userId as number }
});
if(!user || user.refreshToken !== refreshToken) {
  return NextResponse.redirect(new URL('/login', request.url));
}

// 生成新的双Token
const { accessToken: newAccessToken, refreshToken: newRefreshToken } = await createTokens(userId);

// 更新数据库中的refresh token
await prisma.user.update({
  where: { id: userId },
  data: { refreshToken: newRefreshToken }
});

// 设置新的Cookie
response.cookies.set('access_token', newAccessToken, {
  httpOnly: true,
  maxAge: 60*15, // 15分钟
  sameSite: 'strict',
  path: '/'
});
response.cookies.set('refresh_token', newRefreshToken, {
  httpOnly: true,
  maxAge: 60*60*24*7, // 7天
  sameSite: 'strict',
  path: '/'
});

刷新流程的双重验证机制

  1. JWT 签名验证:确保令牌未被篡改且在有效期内
  2. 数据库比对验证:确保该 Refresh Token 是当前有效的令牌(防止已注销的令牌被滥用)

这种双重验证机制极大提高了系统安全性,即使 Refresh Token 被泄露,只要服务器端已更新令牌,攻击者也无法使用旧令牌获取新的 Access Token。

安全特性深度剖析

1. Cookie 安全设置

对 Cookie 的设置多层次的安全考量:

  • httpOnly: true:禁止 JavaScript 访问 Cookie,有效防止 XSS(跨站脚本)攻击
  • sameSite: 'strict' :限制 Cookie 仅在同站点请求中发送,有效防范 CSRF(跨站请求伪造)攻击
  • 差异化过期时间:Access Token(15 分钟)远短于 Refresh Token(7 天),平衡安全性和用户体验
  • path: '/' :确保 Cookie 在整个应用中可用

2. 令牌生命周期管理

  • 短期 Access Token:减少令牌泄露后的风险窗口
  • 定期刷新机制:每 15 分钟自动更新 Access Token
  • Refresh Token 轮换:每次刷新都会生成新的 Refresh Token 并更新数据库,实现 "令牌滑动窗口"

3. 防御机制

  • 双重验证:同时验证令牌签名和数据库存储状态
  • 即时失效:通过删除数据库中的 Refresh Token 可立即撤销用户访问权限
  • 格式验证:登录前验证邮箱和密码格式,减少无效请求

双 Token 机制工作流程图

用户 -> 登录请求 -> 服务器
                     |
                     | 1. 验证凭据
                     v
服务器 -> 生成双Token -> 存储Refresh Token -> 设置Cookie -> 用户
                     |
                     v
用户 -> API请求(带Access Token) -> 服务器
                     |
                     | 2. 验证Access Token
                     v
           有效 -> 处理请求 -> 返回结果
           无效 -> 401错误
                     |
                     v
用户 -> 刷新请求(带Refresh Token) -> 服务器
                     |
                     | 3. 双重验证Refresh Token
                     v
           有效 -> 生成新双Token -> 更新存储和Cookie -> 用户
           无效 -> 重定向到登录页

实际应用中的最佳实践

  1. 令牌存储策略

    • 始终使用 HTTP-only Cookie存储令牌,避免 localStorage 或 sessionStorage
    • 生产环境中必须启用 HTTPS,防止 Cookie 在传输过程中被窃取
  2. 令牌过期时间设置

    • Access Token:15-30 分钟(根据业务敏感度调整)
    • Refresh Token:7-30 天(根据用户体验需求调整)
  3. 前端实现建议

    • 拦截 401 响应,自动发起令牌刷新请求
    • 实现刷新令牌失败后的优雅降级(重定向到登录页)
    • 避免在前端存储令牌相关的敏感信息

总结

JWT 双 Token 机制通过 Access Token 和 Refresh Token 的协同工作,在安全性和用户体验之间取得了平衡。其核心优势在于:

  1. 安全性:短期有效的 Access Token 降低了泄露风险,双重验证机制防止滥用
  2. 灵活性:可随时撤销特定用户的访问权限,无需维护全局黑名单
  3. 用户体验:减少登录频率,同时保持较高的安全标准
  4. 可扩展性:便于支持多设备登录、单点登录等复杂场景

通过本文解析的代码实现,我们可以看到双 Token 机制如何在实际项目中落地。在实际应用中,还需要根据具体业务需求和安全级别,调整令牌有效期、验证策略和防御措施,构建更适合自身系统的认证方案。