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分钟内有效)。加了随机数,可以保证同一个请求只能被处理一次。
签名生成步骤:
- 将所有请求参数按字典序排序
- 拼接成字符串:key1=value1&key2=value2
- 加上时间戳和随机数:key1=value1&key2=value2×tamp=xxx&nonce=yyy
- 用HMAC-SHA256或RSA算签名
- 把签名放在请求头里,比如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安全就有了八成把握。