You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js及Next.js无数据库场景下令牌存储的最佳实践

Node.js 访问令牌与刷新令牌存储最佳实践

通用Node.js场景的令牌存储最佳实践

访问令牌(Access Token)

  • 客户端侧:
    • Web应用:存在HttpOnly、Secure的Cookie中,开启SameSite属性(Strict/Lax),规避XSS和CSRF风险;绝对不能存在localStorage/sessionStorage里。
    • 移动端:存在系统提供的安全密钥存储,比如iOS的Keychain、Android的Keystore,禁止明文存本地文件或SharedPreferences。
    • 单页应用(SPA):仅存在内存中(比如React状态、Vue数据),页面刷新后重新获取,避免持久化带来的安全隐患。
  • 服务端侧:
    • 如果服务端需要持有access token(比如做API代理),优先存在Redis这类内存缓存中,设置与令牌过期时间一致的TTL;别存在进程内存(应用重启就丢失),更不能明文写入文件。

刷新令牌(Refresh Token)

  • 客户端侧:
    • 同access token,用HttpOnly、Secure Cookie存储,绑定用户设备标识,启用令牌轮换机制(每次刷新获取新的refresh token,旧的立即作废)。
  • 服务端侧:
    • 必须加密存储(比如AES加密),绑定用户ID和设备信息;
    • 存放在持久化存储(数据库、Redis)中,设置较短的有效期(比如7-30天);
    • 实现黑名单机制,用于主动撤销被盗或过期的令牌。

Next.js 无数据库的刷新令牌持久化方案

如果不想用传统数据库,且不能将令牌放在.env文件中(.env是静态配置,无法动态更新),可以参考以下几种方案:

方案1:加密文件存储

将加密后的刷新令牌存储在项目目录外的安全文件中,避免提交到版本控制,仅让服务端进程可读。
代码示例(Next.js API路由中实现):

// pages/api/refresh-token-handler.js
const crypto = require('crypto');
const fs = require('fs').promises;
const path = require('path');

// 从.env读取加密密钥(仅服务端可见)
const ENCRYPTION_KEY = process.env.TOKEN_ENCRYPTION_KEY; // 需是32位字符串
const IV_LENGTH = 16;

// 加密函数
function encrypt(text) {
  const iv = crypto.randomBytes(IV_LENGTH);
  const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
  let encrypted = cipher.update(text);
  encrypted = Buffer.concat([encrypted, cipher.final()]);
  return `${iv.toString('hex')}:${encrypted.toString('hex')}`;
}

// 解密函数
function decrypt(text) {
  const [ivHex, encryptedHex] = text.split(':');
  const iv = Buffer.from(ivHex, 'hex');
  const encryptedText = Buffer.from(encryptedHex, 'hex');
  const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(ENCRYPTION_KEY), iv);
  let decrypted = decipher.update(encryptedText);
  decrypted = Buffer.concat([decrypted, decipher.final()]);
  return decrypted.toString();
}

// 保存刷新令牌
export async function saveRefreshToken(token) {
  const encryptedToken = encrypt(token);
  // 存在项目外的安全目录,比如服务器的/var/secure目录
  const tokenPath = path.join('/var/secure', 'nextjs-refresh-token.enc');
  await fs.writeFile(tokenPath, encryptedToken);
  // 设置文件权限为仅所有者可读
  await fs.chmod(tokenPath, 0o600);
}

// 读取刷新令牌
export async function getRefreshToken() {
  try {
    const tokenPath = path.join('/var/secure', 'nextjs-refresh-token.enc');
    const encryptedToken = await fs.readFile(tokenPath, 'utf8');
    return decrypt(encryptedToken);
  } catch (err) {
    // 文件不存在或读取失败时返回null
    return null;
  }
}

注意:部署时要确保目标服务器存在指定的安全目录,且服务进程有读写权限。

方案2:Redis内存存储(轻量替代)

Redis属于内存键值存储,不算传统数据库,支持持久化(RDB/AOF),重启后数据不会丢失,适合需要动态更新令牌的场景。
代码示例:

// pages/api/refresh-token-handler.js
import { createClient } from 'redis';
import crypto from 'crypto';

const ENCRYPTION_KEY = process.env.TOKEN_ENCRYPTION_KEY;
const IV_LENGTH = 16;

// 创建Redis客户端
const redisClient = createClient({ url: process.env.REDIS_URL });
redisClient.connect().catch(console.error);

// 加密解密函数同方案1
// ...

// 保存刷新令牌,设置30天有效期
export async function saveRefreshToken(token) {
  const encryptedToken = encrypt(token);
  await redisClient.set('app_refresh_token', encryptedToken, { EX: 30 * 24 * 60 * 60 });
}

// 读取刷新令牌
export async function getRefreshToken() {
  const encryptedToken = await redisClient.get('app_refresh_token');
  if (!encryptedToken) return null;
  return decrypt(encryptedToken);
}

方案3:云服务商的参数存储服务

如果部署在云环境(比如Vercel、AWS),可以用云提供的参数存储服务,这类服务支持加密存储和动态更新,无需自己管理加密逻辑:

  • Vercel Edge Config:可以在控制台直接更新令牌值,无需重新部署应用,代码示例:
// pages/api/refresh-token-handler.js
import { get, set } from '@vercel/edge-config';

// 保存刷新令牌
export async function saveRefreshToken(token) {
  await set('app_refresh_token', token);
}

// 读取刷新令牌
export async function getRefreshToken() {
  return await get('app_refresh_token');
}
  • AWS SSM Parameter Store:将令牌存在加密的参数中,通过AWS SDK读取和更新,适合AWS部署的场景。

内容的提问来源于stack exchange,提问作者ilyo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 03:05:19