Nest防重放方案

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在 NestJS 架构中,实现 防重放攻击(Replay Attack) 是保护接口安全、提升风控能力的关键环节,尤其是在支付、订单、登录、敏感操作等场景中。


✅ 一、重放攻击的本质

攻击者截获合法请求并 重复发送(Replay),从而实现例如:

  • 重复下单
  • 重复提现
  • 重复授权

✅ 二、大厂标准防重放方案核心要点

防护要素实现方式
请求唯一标识每次请求携带唯一 nonce(UUID + 时间戳)
时间戳限制请求时间不得超过系统容忍的时间窗口(如 60 秒)
服务端验签所有请求参数和 timestamp + nonce 一起签名
签名校验校验 signature 合法性,防止参数被篡改
nonce 缓存将 nonce 缓存在 Redis,一旦使用即失效(幂等性)

✅ 三、NestJS 实现步骤(示例)

1️⃣ 客户端请求结构(示例)

{
  "data": { "amount": 100 },
  "timestamp": "1713772601",
  "nonce": "a1b2c3d4e5f6",
  "sign": "6f1a0e5c84..."
}

2️⃣ 服务端实现防重放拦截器

// src/common/interceptors/replay.interceptor.ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  BadRequestException,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import * as crypto from 'crypto';
import { Redis } from 'ioredis';

@Injectable()
export class ReplayInterceptor implements NestInterceptor {
  constructor(private readonly redis: Redis, private readonly secret: string) {}

  async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const { timestamp, nonce, sign, data } = request.body;

    // 1. 时间戳合法性
    const now = Math.floor(Date.now() / 1000);
    if (Math.abs(now - Number(timestamp)) > 60) {
      throw new BadRequestException('Request expired');
    }

    // 2. nonce 是否重复
    const nonceKey = `replay:nonce:${nonce}`;
    const exists = await this.redis.get(nonceKey);
    if (exists) throw new BadRequestException('Replay detected');
    await this.redis.set(nonceKey, '1', 'EX', 120); // 防止短时间重复请求

    // 3. 签名校验
    const payload = JSON.stringify(data);
    const expectedSign = crypto
      .createHmac('sha256', this.secret)
      .update(`${timestamp}.${nonce}.${payload}`)
      .digest('hex');

    if (sign !== expectedSign) throw new BadRequestException('Invalid signature');

    return next.handle();
  }
}

3️⃣ 应用到指定接口或全局

// 全局或局部注册
app.useGlobalInterceptors(new ReplayInterceptor(redis, process.env.SIGN_SECRET));

或在控制器中使用 @UseInterceptors(ReplayInterceptor)


4️⃣ 中央配置 + 自动绑定(可选)

封装为通用模块:

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useFactory: () => new ReplayInterceptor(redisInstance, process.env.SIGN_SECRET),
    },
  ],
})
export class SecurityModule {}

✅ 四、补充建议(实践)

内容建议
Redis Key设计合理前缀,如 replay:nonce:<nonce>,TTL 建议60-120秒
密钥管理secret 建议从配置中心或 KMS 动态注入
API 限流搭配 @nestjs/throttler,进一步提升防护
安全日志签名失败、重放拦截需日志审计
SDK封装客户端签名逻辑建议封装 SDK,统一集成

✅ 五、签名逻辑建议(客户端 / 服务端一致)

签名规则建议格式为:

SHA256_HMAC(secret, `${timestamp}.${nonce}.${JSON(data)}`)

也可自定义签名规则并文档化,配合 Swagger 提供示例。


那为什么选用Redis

在防重放方案中普遍选择 Redis 作为存储介质,原因主要体现在以下几点:


🚀 1. 高性能、低延迟

  • Redis 的存取速度极快(毫秒级别甚至亚毫秒)。
  • 请求防重放校验一般属于高频请求,每次请求都需要校验nonce是否被使用,高性能的 Redis 可最大程度减少额外延迟。

🔗 2. 分布式场景支持

  • Redis 天然支持分布式部署。
  • 在微服务架构、多实例部署时,使用本地内存存储无法保证一致性,而 Redis 可以天然地支持共享 nonce 数据。
  • 多个 NestJS 实例可以共用 Redis 实现全局统一的防重放机制。

⏳ 3. 原生支持 TTL(过期时间)

  • Redis 提供原生的 key 过期机制(TTL)。
  • 对 nonce 自动过期管理非常友好,不需要额外代码实现。
  • nonce 一般只需要短暂保存(如 60~120秒),Redis 的 TTL 特性极大降低了开发复杂度。

示例:

await redis.set(nonceKey, '1', 'EX', 120);

⚡️ 4. 内存存储,高并发场景优势明显

  • Redis 基于内存存储,高并发场景吞吐能力卓越,适合大量并发请求。
  • 防重放是典型高并发验证场景,MySQL 或其他磁盘型数据库性能明显不足。

📈 5. 横向扩展与高可用性

  • Redis Cluster 或 Redis Sentinel 可以轻松实现高可用性和扩展。
  • 即使 Redis 节点故障,也不会影响整体服务的稳定性。

🔐 6. 成熟且稳定的技术栈

  • Redis 是主流企业广泛使用的缓存与中间件服务,可靠性、稳定性高。
  • 企业级开源社区和商业版支持成熟完善。

🔍 7. 易于监控和观测

  • Redis 提供了丰富的监控指标,可以轻松整合到监控体系(如 Prometheus + Grafana)。
  • 可快速定位防重放机制的热点问题或异常。

🛠️ Redis 对比其他方案(如本地缓存或数据库):

对比维度Redis本地内存缓存MySQL等磁盘数据库
速度与延迟✅ 极快✅ 快❌ 较慢
分布式支持✅ 支持❌ 不支持(实例独立)✅ 支持但复杂性高
数据一致性✅ 优秀❌ 较差(实例隔离)✅ 优秀
TTL过期特性✅ 原生✅ 需要额外实现❌ 需要额外实现
高并发处理能力✅ 极高✅ 高(但不分布式)❌ 一般
监控能力✅ 丰富❌ 一般✅ 丰富但成本较高

💡 总结:为什么 Redis 最适合企业级别的防重放?

企业级别普遍场景:

  • 高并发
  • 分布式架构
  • 严格性能需求

这些场景中,Redis 的高效能、分布式特性、TTL 特性完美满足需求,几乎是大厂实施防重放机制的最佳实践标准。

这也是为什么防重放方案普遍选用 Redis 的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TE-茶叶蛋

踩坑不易,您的打赏,感谢万分

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值