基于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=18006. 注意事项
- 密钥管理:建议使用 KMS 或 HashiCorp Vault 管理 TOKEN_SECRET
- 集群部署:Redis 应配置为集群模式确保高可用
- 监控指标:需要监控 Redis 内存使用率和 JWT 解密失败率
- 算法升级:从 HS256 迁移到 RS256 时需要无缝过渡方案
7. 总结
本方案实现了:
✅ 分布式令牌存储
✅ 自动化的凭证生命周期管理
✅ 企业级安全防护
✅ 高可用服务架构
适用于需要严格访问控制的 API 服务、微服务鉴权中心等场景,为系统安全提供坚实保障。
基于Redis与JWT的高安全认证服务实现
https://rollday.site/2025/04/05/基于Redis与JWT的高安全认证服务实现/