4、API安全防护:API网关与限流熔断、JWT令牌管理、API签名与防重放攻击

聊到低代码平台的API安全,我脑子里第一个蹦出来的词就是「四面漏风」。你想想看,低代码平台天生就要暴露大量API给前端、给第三方、给各种自动化脚本。每个API都是一扇门,门开多了,总得有人守着。

这一章,咱们就聊聊怎么把这些门守好。我会从三个维度展开:API网关怎么搭、JWT令牌怎么管、签名防重放怎么做。嗯,都是我在实际项目中踩过坑的地方。

4.1 API网关:不只是反向代理

很多人觉得API网关就是个反向代理,把请求转发到后端完事。其实不然。我在一个低代码项目中,网关承担了至少五个角色:认证、限流、熔断、日志、协议转换。

说白了,网关是你的第一道防线。所有外部请求都得先过它这一关。

核心职责:

  • 统一认证:所有请求先过网关,网关校验JWT或签名,不合规的直接挡在外面
  • 限流熔断:防止某个租户的突发流量打垮整个系统
  • 请求审计:记录谁、在什么时间、调了什么API、返回了什么结果
  • 协议转换:比如把外部的RESTful请求转成内部的gRPC调用

我习惯用Kong或者Spring Cloud Gateway来做这件事。选型时主要看两点:一是插件生态是否丰富,二是性能损耗能不能接受。

4.2 限流与熔断:别让一个租户拖垮所有人

低代码平台有个典型场景:某个租户写了个死循环的自动化脚本,疯狂调用API。如果没有限流,整个集群都可能被拖垮。我曾经遇到过这种情况,那叫一个惨烈——所有租户的页面都打不开了,后台日志全是超时。

从那以后,我对限流格外上心。常用的限流算法有四种:

算法 原理 适用场景
令牌桶 匀速往桶里放令牌,请求来了就拿令牌,拿不到就等或拒绝 允许突发流量,但总体可控
漏桶 请求先进桶,桶以固定速率往外漏,桶满了就丢请求 流量整形,适合需要平滑输出的场景
滑动窗口 把时间切成小段,统计每个小段内的请求数 精确控制,避免临界突变
计数器 固定时间窗口内计数,超了直接拒绝 简单粗暴,适合粗粒度控制

我个人偏爱令牌桶+滑动窗口的组合。令牌桶应对突发,滑动窗口做精细化控制。举个例子:

// 伪代码:基于令牌桶的限流
RateLimiter limiter = RateLimiter.create(100); // 每秒100个令牌
if (limiter.tryAcquire()) {
    // 处理请求
} else {
    // 返回429 Too Many Requests
    return "请求太频繁,请稍后再试";
}

避坑指南:我曾经把限流阈值设得太死,结果正常用户也被误伤。后来我加了「预热」机制——新租户刚接入时,限流阈值从低到高慢慢放开。这样既防住了恶意请求,又不影响正常使用。

熔断和限流是两回事。限流是主动控制,熔断是被动保护。当某个下游服务连续出错,熔断器就会跳闸,直接返回降级结果,不再继续调用。我常用的熔断框架是Hystrix和Resilience4j。

4.3 JWT令牌管理:别把密钥写在代码里

JWT这东西,用好了是神器,用不好是灾难。我见过太多人把JWT密钥硬编码在代码里,然后提交到GitHub上。嗯,那画面太美我不敢看。

JWT的核心结构就三部分:Header、Payload、Signature。Header声明算法,Payload放用户信息,Signature用来防篡改。

JWT安全实践:

  • 密钥管理:用专门的密钥管理服务(如Vault、KMS),别写在配置文件里
  • 过期时间:Access Token设短一点,15分钟到1小时。Refresh Token可以长一些,但也要有上限
  • Payload别放敏感信息:JWT是Base64编码的,不是加密的。谁都能解码看内容
  • 签名算法别用HS256:HS256是对称签名,密钥泄露就全完了。用RS256或ES256,公私钥分离

我习惯的做法是:Access Token用RS256签名,公钥放在网关,私钥放在认证服务。网关拿到JWT后,用公钥验证签名,不需要每次都去认证服务查一遍。这样性能好,也安全。

// 生成JWT(认证服务端)
String jwt = Jwts.builder()
    .setSubject(userId)
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1小时过期
    .signWith(SignatureAlgorithm.RS256, privateKey)
    .compact();

// 验证JWT(网关端)
Claims claims = Jwts.parser()
    .setSigningKey(publicKey)
    .parseClaimsJws(token)
    .getBody();

注意:JWT一旦签发就无法撤销。如果用户被盗了,你只能等它过期。所以过期时间一定要短。实在需要提前失效的,可以用黑名单机制——把失效的JWT的jti(JWT ID)存到Redis里,网关每次校验时查一下黑名单。

4.4 API签名与防重放攻击

JWT解决了「谁在调用」的问题,但没解决「请求是否被篡改」的问题。API签名就是干这个的。

签名流程其实不复杂:客户端把请求参数按一定规则排序,加上时间戳和随机数,然后用密钥算一个签名。服务端收到后,用同样的方式算一遍,比对签名是否一致。

为什么要加时间戳和随机数?为了防止重放攻击。攻击者截获了你的请求,虽然不知道密钥,但他可以原封不动地再发一次。加了时间戳,可以限制请求的有效期(比如5分钟内有效)。加了随机数,可以保证同一个请求只能被处理一次。

签名生成步骤:

  1. 将所有请求参数按字典序排序
  2. 拼接成字符串:key1=value1&key2=value2
  3. 加上时间戳和随机数:key1=value1&key2=value2×tamp=xxx&nonce=yyy
  4. 用HMAC-SHA256或RSA算签名
  5. 把签名放在请求头里,比如X-Signature
// 服务端验签逻辑(伪代码)
public boolean verifySignature(HttpRequest request) {
    String timestamp = request.getHeader("X-Timestamp");
    String nonce = request.getHeader("X-Nonce");
    String signature = request.getHeader("X-Signature");
    
    // 1. 检查时间戳是否在有效期内(比如5分钟)
    if (Math.abs(System.currentTimeMillis() - Long.parseLong(timestamp)) > 300000) {
        return false;
    }
    
    // 2. 检查nonce是否已被使用(存Redis,设置过期时间)
    if (redis.exists(nonce)) {
        return false; // 重放攻击
    }
    redis.setex(nonce, 300, "used");
    
    // 3. 重新计算签名并比对
    String expectedSign = calculateSign(request.getParams(), timestamp, nonce, secretKey);
    return expectedSign.equals(signature);
}

避坑指南:我曾经遇到过一个问题——验签时忘了考虑请求体的顺序。客户端和服务端对JSON字段的排序不一致,导致签名总对不上。后来我统一要求:所有请求参数必须按字典序排序,JSON字段也按字母顺序排列。嗯,这种细节最坑人。

4.5 组合拳:网关+JWT+签名

这三者不是互斥的,而是互补的。我通常这样组合:

  • 外部API(第三方调用):用API签名+时间戳+随机数,防重放防篡改
  • 内部API(前端调用):用JWT做身份认证,网关做限流熔断
  • 管理API(后台调用):JWT+签名双重校验,再加IP白名单

说白了,安全没有银弹。不同的场景用不同的策略,组合起来才能形成纵深防御。我见过一些团队只做了JWT,觉得万事大吉,结果被重放攻击打得措手不及。也见过只做签名不做限流的,被爬虫爬得服务器直接宕机。

嗯,这一章的内容就这些。API安全是个大话题,但核心思路就三个字:防、控、管。防住外部攻击,控制内部流量,管好身份令牌。把这三点做好了,低代码平台的API安全就有了八成把握。