基于Redis与JWT的高安全认证服务实现

本文最后更新于 2025年4月5日 上午

1. 前言

在现代 Web 应用中,安全可靠的认证系统是核心基础设施。本文展示了一个基于 Redis 与 JWT 的认证服务实现方案,具备以下特性:

  • 分布式令牌存储(Redis Cluster 支持)
  • JWT 令牌自动过期与强制吊销
  • 全链路错误重试机制
  • 生产级安全防护措施
  • 优雅的进程关闭处理

2.完整代码

const { createClient } = require("redis");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const dotenv = require("dotenv");

// 增强配置验证
dotenv.config();
const ENV = process.env;
const REDIS_URL = ENV.REDIS_URL || "redis://localhost:6379";
const TOKEN_SECRET = ENV.TOKEN_SECRET;
const TOKEN_EXPIRY = ENV.TOKEN_EXPIRY || "1h";
const TOKEN_ALGORITHM = ENV.TOKEN_ALGORITHM || "HS256"; // 明确指定算法
const REDIS_EXPIRY = parseInt(ENV.REDIS_EXPIRY) || 3600;

// 参数校验强化
if (!TOKEN_SECRET || TOKEN_SECRET.length < 32) {
  console.error("FATAL: TOKEN_SECRET 必须为至少32位的字符串");
  process.exit(1);
}

if (!/^\d+[smh]$/.test(TOKEN_EXPIRY)) {
  console.error("FATAL: TOKEN_EXPIRY 格式错误,示例: 60s, 5m, 1h");
  process.exit(1);
}

// 将 JWT 过期时间转换为秒数用于校验
const tokenExpiryToSeconds = (expiry) => {
  const unit = expiry.slice(-1);
  const value = parseInt(expiry.slice(0, -1));
  const multipliers = { s: 1, m: 60, h: 3600 };
  return value * multipliers[unit];
};

const jwtExpirySeconds = tokenExpiryToSeconds(TOKEN_EXPIRY);
if (REDIS_EXPIRY < jwtExpirySeconds) {
  console.error(
    `FATAL: REDIS_EXPIRY (${REDIS_EXPIRY}) 必须 >= TOKEN_EXPIRY (${jwtExpirySeconds}s)`
  );
  process.exit(1);
}

// 强化 Redis 配置
const redisClient = createClient({
  url: REDIS_URL,
  socket: {
    reconnectStrategy: (attempts) => Math.min(attempts * 500, 5000), // 指数退避重连
    tls: REDIS_URL.startsWith("rediss://")
      ? {
          servername: new URL(REDIS_URL).hostname,
          rejectUnauthorized: true,
        }
      : undefined,
  },
});

// 连接状态管理增强
let isRedisReady = false;
const connectionLock = { pending: false };

const connectRedis = async () => {
  if (isRedisReady || connectionLock.pending) return;
  connectionLock.pending = true;

  try {
    await redisClient.connect();
    isRedisReady = true;
  } catch (err) {
    console.error("Redis 连接失败:", err.message);
  } finally {
    connectionLock.pending = false;
  }
};

redisClient
  .on("ready", () => (isRedisReady = true))
  .on("end", () => (isRedisReady = false))
  .on("error", (err) => console.error("Redis 错误:", err.message));

// 安全操作包装器(带重试机制)
const safeRedis = async (operation, retries = 3) => {
  await connectRedis();

  for (let i = 0; i < retries; i++) {
    try {
      return await operation();
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise((res) => setTimeout(res, 100 * (i + 1)));
    }
  }
};

// 令牌校验正则
const JWT_REGEX = /^[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/;

class AuthProvider {
  static async generate(data) {
    try {
      // 强化输入校验
      if (typeof data !== "string" || data.length === 0 || data.length > 256) {
        return { state: "invalid_input" };
      }

      const payload = {
        data: data,
        timestamp: Date.now(),
        nonce: crypto.randomBytes(32).toString("hex"), // 增强随机性
      };

      const token = jwt.sign(payload, TOKEN_SECRET, {
        algorithm: TOKEN_ALGORITHM,
        expiresIn: TOKEN_EXPIRY,
      });

      // 验证 Redis 写入结果
      const result = await safeRedis(() =>
        redisClient.set(
          token,
          JSON.stringify(payload),
          "EX",
          REDIS_EXPIRY,
          "NX"
        )
      );

      if (result !== "OK") {
        console.error("令牌存储失败");
        return { state: "error" };
      }

      return { state: "issued", payload: { token } }; // 减少返回信息
    } catch (err) {
      console.error(`生成令牌失败: ${err.code || "UNKNOWN"}`);
      return { state: "error" };
    }
  }

  static async verify(token) {
    try {
      // 增强令牌格式校验
      if (
        typeof token !== "string" ||
        token.length > 2048 ||
        !JWT_REGEX.test(token)
      ) {
        return { state: "invalid" };
      }

      const decoded = jwt.verify(token, TOKEN_SECRET, {
        algorithms: [TOKEN_ALGORITHM],
      });
      const exists = await safeRedis(() => redisClient.exists(token));

      if (exists !== 1) return { state: "revoked" };

      return {
        state: "valid",
        payload: {
          data: decoded.data,
          expiresAt: new Date(decoded.exp * 1000),
        },
      };
    } catch (err) {
      return this.#handleVerifyError(err);
    }
  }

  static #handleVerifyError(err) {
    if (err.name === "TokenExpiredError") {
      return { state: "expired" };
    }
    if (err.name === "JsonWebTokenError") {
      return { state: "invalid" };
    }
    console.error(`验证错误[${err.code || "UNKNOWN"}]:`, err.message);
    return { state: "error" };
  }

  static async revoke(token) {
    try {
      if (!JWT_REGEX.test(token)) return { state: "invalid" };

      const result = await safeRedis(() => redisClient.del(token));
      return result === 1 ? { state: "revoked" } : { state: "not_found" };
    } catch (err) {
      console.error("吊销失败:", err.message);
      return { state: "error" };
    }
  }
}

// 增强关闭处理
const shutdown = async () => {
  if (isRedisReady) {
    await redisClient.quit().catch(() => {});
  }
  await new Promise((resolve) => setTimeout(resolve, 500));
  process.exit();
};

["SIGINT", "SIGTERM", "uncaughtException"].forEach((event) => {
  process.on(event, async (err) => {
    if (err) console.error("未捕获异常:", err.message);
    await shutdown();
  });
});

module.exports = { AuthProvider };

3. 核心代码实现

const { createClient } = require("redis");
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
const dotenv = require("dotenv");

// 增强配置验证
dotenv.config();
const ENV = process.env;
/* 此处完整代码见问题描述部分 */

4. 架构设计解析

4.1 安全配置体系

// 环境变量验证
if (!TOKEN_SECRET || TOKEN_SECRET.length < 32) {
  console.error("FATAL: TOKEN_SECRET 必须为至少32位的字符串");
  process.exit(1);
}

// 算法配置
const TOKEN_ALGORITHM = ENV.TOKEN_ALGORITHM || "HS256";
  • 密钥安全:强制要求 32 位以上密钥,防止暴力破解
  • 算法选择:支持 HS256/RS256 等标准算法,杜绝使用 none 算法
  • 双过期策略:JWT 过期时间与 Redis 存储时间双重保障

4.2 Redis 连接管理

const redisClient = createClient({
  url: REDIS_URL,
  socket: {
    reconnectStrategy: (attempts) => Math.min(attempts * 500, 5000),
    tls: REDIS_URL.startsWith("rediss://")
      ? {
          /* TLS配置 */
        }
      : undefined,
  },
});

// 连接状态机管理
let isRedisReady = false;
const connectionLock = { pending: false };
  • 指数退避重连:网络异常时自动重连,避免雪崩效应
  • TLS 支持:自动识别 rediss 协议启用加密传输
  • 连接状态锁:防止重复连接造成资源浪费

4.3 认证服务核心类

4.3.1 令牌生成

static async generate(data) {
  const payload = {
    data: data,
    timestamp: Date.now(),
    nonce: crypto.randomBytes(32).toString("hex")
  };

  const token = jwt.sign(payload, TOKEN_SECRET, {
    algorithm: TOKEN_ALGORITHM,
    expiresIn: TOKEN_EXPIRY
  });

  await redisClient.set(token, JSON.stringify(payload), "EX", REDIS_EXPIRY);
}
  • 随机数注入:使用 32 字节加密随机数防止重放攻击
  • 写前验证:Redis NX 参数防止令牌覆盖

4.3.2 令牌验证

static async verify(token) {
  if (!JWT_REGEX.test(token)) return { state: "invalid" };

  const decoded = jwt.verify(token, TOKEN_SECRET, {
    algorithms: [TOKEN_ALGORITHM]
  });

  const exists = await redisClient.exists(token);
  return exists === 1 ? "valid" : "revoked";
}
  • 格式预检:正则表达式过滤非法令牌格式
  • 双重验证:JWT 解密成功+Redis 存在性检查

4.4 容错处理机制

// 安全操作包装器(带重试机制)
const safeRedis = async (operation, retries = 3) => {
  for (let i = 0; i < retries; i++) {
    try {
      return await operation();
    } catch (err) {
      if (i === retries - 1) throw err;
      await new Promise(res => setTimeout(res, 100 * (i + 1)));
    }
  }
};

// 统一错误处理
static #handleVerifyError(err) {
  switch(err.name) {
    case "TokenExpiredError": return "expired";
    case "JsonWebTokenError": return "invalid";
    default: return "error";
  }
}
  • 自动重试:网络波动时自动进行 3 次重试
  • 错误分类:精准识别过期、无效等不同错误类型

5. 服务部署示例

# 环境变量配置示例
REDIS_URL="rediss://cluster.example.com:6379"
TOKEN_SECRET="d7f8a9b2c4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b"
TOKEN_EXPIRY="15m"
REDIS_EXPIRY=1800

6. 注意事项

  1. 密钥管理:建议使用 KMS 或 HashiCorp Vault 管理 TOKEN_SECRET
  2. 集群部署:Redis 应配置为集群模式确保高可用
  3. 监控指标:需要监控 Redis 内存使用率和 JWT 解密失败率
  4. 算法升级:从 HS256 迁移到 RS256 时需要无缝过渡方案

7. 总结

本方案实现了:
✅ 分布式令牌存储
✅ 自动化的凭证生命周期管理
✅ 企业级安全防护
✅ 高可用服务架构

适用于需要严格访问控制的 API 服务、微服务鉴权中心等场景,为系统安全提供坚实保障。


基于Redis与JWT的高安全认证服务实现
https://rollday.site/2025/04/05/基于Redis与JWT的高安全认证服务实现/
作者
RollDay
发布于
2025年4月5日
更新于
2025年4月5日
许可协议