SpringBoot + 防重放攻击 + 请求签名:API 接口防刷、防篡改、防重复提交
今天咱们聊聊一个在API安全中非常关键的话题:如何保护我们的接口不被恶意攻击。
API安全面临的威胁
在我们的日常开发工作中,经常会遇到这样的安全威胁:
- 接口被刷:恶意用户通过脚本大量调用接口,消耗服务器资源
- 数据被篡改:请求参数在传输过程中被中间人修改
- 重复提交:同一笔交易被恶意重复提交,造成经济损失
- 身份冒充:攻击者伪造合法用户身份进行操作
传统的认证方式往往只能解决身份验证问题,对于请求的完整性和防重放攻击无能为力。今天我们就来聊聊如何构建一套完整的API安全防护体系。
核心防护机制
1. 请求签名机制
请求签名就像是给每个请求贴上一个独一无二的"身份证",确保请求的完整性和真实性:
- 客户端将请求参数按照约定规则拼接并加密生成签名
- 服务端收到请求后,按照同样规则验证签名
- 如果签名不匹配,说明请求被篡改,直接拒绝
2. 防重放攻击
防重放攻击的核心是确保每个请求只能被处理一次:
- 为每个请求生成唯一的nonce(随机数)
- 结合时间戳,确保请求在有效期内
- 服务端记录已处理的请求,防止重复处理
3. 多层防护
我们构建的是一个多层次的安全防护体系:
- 接入层:IP限流、请求频率控制
- 认证层:身份验证、权限校验
- 应用层:参数校验、业务逻辑验证
实现方案详解
1. 签名算法设计
签名算法是整个安全体系的核心,我们采用HMAC-SHA256算法:
public class SignatureUtil {
public static String generateSignature(Map<String, String> params, String secretKey) {
// 参数排序
String sortedParams = sortParameters(params);
// 拼接待签名字符串
String signStr = sortedParams + "&secret=" + secretKey;
// 生成签名
return hmacSHA256(signStr, secretKey);
}
private static String sortParameters(Map<String, String> params) {
// 按照参数名排序并拼接
return params.entrySet().stream()
.filter(entry -> !"sign".equals(entry.getKey())) // 排除签名字段
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
}
}
2. 防重放机制实现
为了防止请求被重复使用,我们引入时间窗口和唯一标识:
@Component
public class ReplayAttackPrevention {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public boolean validateNonce(String nonce, long timestamp) {
// 检查时间戳是否在有效期内(如5分钟内)
long currentTime = System.currentTimeMillis();
if (currentTime - timestamp > 5 * 60 * 1000) {
return false; // 超过有效期
}
// 检查nonce是否已使用过
String key = "nonce:" + nonce;
Boolean exists = redisTemplate.hasKey(key);
if (Boolean.TRUE.equals(exists)) {
return false; // nonce已存在,防止重放
}
// 设置nonce有效期,防止内存占用过大
redisTemplate.opsForValue().set(key, "1", Duration.ofMinutes(10));
return true;
}
}
3. 拦截器集成
将安全验证逻辑集成到Spring的拦截器中:
@Component
public class ApiSecurityInterceptor implements HandlerInterceptor {
@Autowired
private SignatureValidator signatureValidator;
@Autowired
private ReplayAttackPrevention replayProtection;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 1. 验证签名
if (!signatureValidator.validate(request)) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getWriter().write("{\"error\":\"Invalid signature\"}");
return false;
}
// 2. 防重放验证
String nonce = request.getHeader("X-Nonce");
String timestamp = request.getHeader("X-Timestamp");
if (!replayProtection.validateNonce(nonce, Long.parseLong(timestamp))) {
response.setStatus(HttpStatus.BAD_REQUEST.value());
response.getWriter().write("{\"error\":\"Replay attack detected\"}");
return false;
}
return true;
}
}
4. 客户端实现
客户端需要按照约定生成签名和必要参数:
public class ApiClient {
public ApiResponse callApi(ApiRequest request, String appKey, String secretKey) {
// 添加必要参数
request.addParam("app_key", appKey);
request.addParam("timestamp", String.valueOf(System.currentTimeMillis()));
request.addParam("nonce", UUID.randomUUID().toString());
// 生成签名
String signature = SignatureUtil.generateSignature(request.getParams(), secretKey);
request.addParam("sign", signature);
// 发送请求
return httpClient.post(request);
}
}
高级防护策略
1. IP限流
结合令牌桶算法对IP进行限流:
@Component
public class IpRateLimiter {
public boolean tryAcquire(String ip, int permits) {
String key = "rate_limit:" + ip;
// 使用Redis的Lua脚本实现原子操作
return executeRateLimitScript(key, permits);
}
}
2. 用户行为分析
通过分析用户的历史行为,识别异常请求模式:
- 频率异常:单位时间内请求次数过多
- 时间异常:在非正常时间段大量请求
- 参数异常:相同参数重复提交
3. 动态密钥管理
实现密钥的动态更新,提高安全性:
- 定期更换密钥
- 支持多版本密钥共存
- 密钥失效后的优雅降级
最佳实践建议
- 密钥安全管理:密钥不要硬编码在代码中,使用配置中心或密钥管理服务
- 性能考虑:安全验证逻辑尽量高效,避免影响正常业务性能
- 监控告警:对异常请求进行监控和告警
- 日志记录:详细记录安全相关的操作日志
通过这套完整的安全防护体系,我们可以有效抵御各种API攻击,保护我们的系统免受恶意侵害。
以上就是本期分享的内容,希望对你有所帮助。更多技术干货,请关注服务端技术精选,我们下期再见!
标题:SpringBoot + 防重放攻击 + 请求签名:API 接口防刷、防篡改、防重复提交
作者:jiangyi
地址:http://jiangyi.space/articles/2026/01/29/1769577061239.html
0 评论