支付网关层轻量校验拦截案例

117 阅读5分钟

《电商支付网关层轻量校验拦截案例及效果》

支付网关层轻量校验拦截案例

某电商支付网关日均处理请求超 1 亿次,通过在网关层前置拦截无效请求,将业务系统的无效请求占比从 30% 降至 5% 以下。以下是具体校验场景与实现:

一、签名校验:拦截伪造请求

场景

黑客通过抓包获取支付请求参数后,篡改金额(如将 100 元改为 1 元)并重新发送,企图恶意下单。

校验逻辑
  1. 商户调用支付接口前,需用双方约定的密钥对请求参数(如订单号、金额、时间戳)按 ASCII 排序后拼接,生成签名(如 MD5 或 SHA256)。

  2. 网关层接收请求时,提取参数与签名,用相同密钥和算法重新计算签名,对比一致则放行,否则拦截。

示例代码(基于 Spring Cloud Gateway)
@Component
public class SignatureVerifyFilter implements GlobalFilter {
  @Override
  public Mono\<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      // 提取请求参数
      MultiValueMap\<String, String> params = exchange.getRequest().getQueryParams();
      String requestSign = params.getFirst("sign");
      String merchantId = params.getFirst("merchantId");


      // 1. 校验签名是否存在
      if (StringUtils.isEmpty(requestSign)) {
          return returnError(exchange, "签名缺失");
      }


      // 2. 获取商户密钥(从配置中心获取)
      String secretKey = merchantConfigService.getSecretKey(merchantId);
      if (StringUtils.isEmpty(secretKey)) {
          return returnError(exchange, "商户不存在");
      }


      // 3. 重新计算签名
      String calculatedSign = SignatureUtils.calculateSign(params, secretKey);
      if (!calculatedSign.equals(requestSign)) {
          return returnError(exchange, "签名错误");
      }
      return chain.filter(exchange);
  }

  private Mono\<Void> returnError(ServerWebExchange exchange, String message) {

      exchange.getResponse().setStatusCode(HttpStatus.BAD\_REQUEST);
      return exchange.getResponse().writeWith(Mono.just(
          exchange.getResponse().bufferFactory().wrap(message.getBytes())
      ));
  }
}
效果
  • 拦截率:日均拦截约 50 万次伪造请求,占总请求的 5%。

  • 业务系统收益:无需处理伪造请求,CPU 使用率降低 15%。

二、参数合法性检查:拦截无效数据

场景

用户误输入金额(如负数、超过最大限额),或恶意传入超长字符串(如订单号长度超过 32 位),导致下游系统解析异常。

校验逻辑
  1. 网关层通过预定义的参数规则(如金额 > 0、订单号格式为字母 + 数字、长度 16-32 位)校验。

  2. 对必填参数(如订单号、支付方式)进行非空检查,缺失则直接拦截。

示例配置(基于 JSON Schema)
// 支付请求参数校验规则(存于配置中心)


{
"type": "object",
"properties": {
  "orderNo": { "type": "string", "pattern": "^\[A-Za-z0-9]{16,32}\$" },
  "amount": { "type": "number", "minimum": 0.01, "maximum": 100000 },
  "payType": { "type": "string", "enum": \["alipay", "wechat", "unionpay"] }
},
"required": \["orderNo", "amount", "payType"]
}
效果
  • 拦截率:日均拦截 100 万次参数错误请求(如金额为 0、支付方式不存在),占总请求的 10%。

  • 下游影响:避免业务系统因参数错误抛出异常,减少 50% 的日志量。

三、防重放攻击:拦截重复请求

场景

黑客重复发送同一支付请求(如用户已支付 100 元,黑客重复提交该请求,企图扣两次款)。

校验逻辑
  1. 客户端请求时携带nonce(随机字符串,如 UUID)和timestamp(时间戳,精确到秒)。
  2. 网关层检查:
  • 时间戳与当前时间差是否超过 5 分钟(防止过期请求)。

  • Redis 中是否存在nonce+merchantId的键(存在则为重复请求),不存在则存入 Redis 并设置 5 分钟过期。

示例代码
@Component
public class AntiReplayFilter implements GlobalFilter {
  @Autowired
  private StringRedisTemplate redisTemplate;
  @Override
  public Mono\<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      MultiValueMap\<String, String> params = exchange.getRequest().getQueryParams();
      String nonce = params.getFirst("nonce");
      String timestamp = params.getFirst("timestamp");
      String merchantId = params.getFirst("merchantId");

      // 1. 校验时间戳有效性(5分钟内)
      long currentTime = System.currentTimeMillis() / 1000;
      if (Math.abs(currentTime - Long.parseLong(timestamp)) > 300) {
          return returnError(exchange, "请求已过期");
      }

      // 2. 校验nonce是否重复
      String redisKey = "pay:nonce:" + merchantId + ":" + nonce;
      Boolean isExist = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", 300, TimeUnit.SECONDS);
      if (isExist == null || !isExist) {
          return returnError(exchange, "重复请求");
      }
      return chain.filter(exchange);
  }
}
效果
  • 拦截案例:某大促期间拦截 20 万次重复支付请求,避免用户重复扣款投诉。

  • 关键指标:重复请求占比从 8% 降至 1% 以下。

四、风控规则:拦截黑名单 IP

场景

某 IP 地址 1 小时内发送 1000 次支付请求(远超正常用户行为),疑似恶意刷单或 DDoS 攻击。

校验逻辑
  1. 网关层通过 Redis 记录每个 IP 的请求次数(滑动窗口计数,如 1 分钟内最多 60 次)。

  2. 结合风控系统的黑名单(如历史有欺诈记录的 IP),直接拦截。

示例代码
@Component
public class IpRateLimitFilter implements GlobalFilter {
  @Autowired
  private StringRedisTemplate redisTemplate;

  @Override
  public Mono\<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      String clientIp = getClientIp(exchange);
      // 1. 检查是否在黑名单
      Boolean isBlack = redisTemplate.hasKey("risk:blacklist:ip:" + clientIp);
      if (Boolean.TRUE.equals(isBlack)) {
          return returnError(exchange, "IP已被限制");
      }

      // 2. 滑动窗口计数(1分钟内最多60次请求)
      String key = "rate:ip:" + clientIp + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmm"));
      Long count = redisTemplate.opsForValue().increment(key);
      if (count != null && count == 1) {
          redisTemplate.expire(key, 60, TimeUnit.SECONDS);
      }

      if (count != null && count > 60) {
          return returnError(exchange, "请求过于频繁");
      }
      return chain.filter(exchange);
  }
}
效果
  • 拦截率:日均拦截 30 万次异常 IP 请求,占总请求的 3%。

  • 安全收益:成功拦截某团伙控制的 1000 个 IP 发起的刷单攻击。

五、校验顺序与性能优化

  1. 执行顺序:按轻量到复杂排序(参数合法性→时间戳→签名→防重放→风控),尽早拦截无效请求。

  2. 性能优化

  • 签名校验等 CPU 密集操作:用本地缓存(Caffeine)缓存商户密钥,减少 DB 查询。

  • 计数类操作:用 Redis Pipeline 批量执行命令,降低网络开销。

  • 整体耗时:所有校验合计耗时 < 5ms,不影响正常请求响应速度。

总结

网关层通过上述轻量校验,日均拦截约 200 万次无效请求,占总请求量的 20% 以上,使业务系统能聚焦于核心支付逻辑。关键价值在于:

  • 安全性:拦截伪造、重复、恶意请求,保障资金安全。

  • 性能:减少下游系统的无效计算与 IO 开销,提升整体吞吐量。

  • 可维护性:集中化校验规则,无需各业务系统重复实现。