實戰(zhàn)出真知!SpringBoot 接口級防護:限流、重放攻擊與簽名機制全解析
在當(dāng)今數(shù)字化服務(wù)廣泛開放的背景下,后端接口往往暴露在公網(wǎng)之下,極易成為攻擊者的突破口。接口調(diào)用的重放攻擊、參數(shù)偽造、暴力請求等問題屢見不鮮,嚴重威脅服務(wù)安全與數(shù)據(jù)完整性。
本文將基于 Spring Boot 框架,手把手實現(xiàn)一套可落地的接口安全防護機制,涵蓋簽名驗證、防重放、限流控制等核心能力,適用于 B 端開放接口、系統(tǒng)對接場景。
接口簽名驗證設(shè)計思路
簽名的目標(biāo)是確?!刚埱笪幢淮鄹?+ 來源可信 + 有效時間內(nèi)唯一調(diào)用」。其基本設(shè)計流程如下:
請求參數(shù)說明
參數(shù)名 | 說明 |
| 接口調(diào)用方唯一標(biāo)識 |
| 請求時間戳,用于防止過期請求 |
| 隨機字符串,確保請求唯一性 |
| 簽名值,由后端根據(jù)參數(shù)與秘鑰生成 |
簽名生成算法
簽名算法一般為:
sign = MD5(按 key 排序后的參數(shù)字符串 + appSecret)參數(shù)拼接規(guī)則舉例:
appId=123&nonce=xyz&t=1710001234&data=xxx&appSecret=abcDEF123接口限流與重放攻擊防護
限流實現(xiàn)(Redis 簡單計數(shù)法)
String key = "rate_limit:" + normalizedPath + ":" + getClientIp(request);
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, 10, TimeUnit.SECONDS);
}
if (count > 5) {
throw new ApiException("請求過于頻繁,請稍后再試");
}重放防護(基于 nonce + timestamp)
String nonceKey = "nonce_cache:" + appId + ":" + nonce;
Boolean success = redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", 5, TimeUnit.MINUTES);
if (Boolean.FALSE.equals(success)) {
throw new ApiException("重復(fù)請求被攔截");
}Spring Boot + 自定義注解 + 攔截器實現(xiàn)簽名校驗
接下來我們通過完整代碼實現(xiàn)接口簽名機制,確保項目中可直接落地。
步驟 1:定義簽名校驗注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ApiSign {
boolean required() default true;
}步驟 2:創(chuàng)建攔截器處理邏輯
@Component
public class ApiSecurityInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final long EXPIRE_TIME = 5 * 60; // 5分鐘
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (!(handler instanceof HandlerMethod)) return true;
HandlerMethod method = (HandlerMethod) handler;
ApiSign apiSign = method.getMethodAnnotation(ApiSign.class);
if (apiSign == null || !apiSign.required()) return true;
// 提取參數(shù)
String appId = request.getParameter("appId");
String sign = request.getParameter("sign");
String nonce = request.getParameter("nonce");
String timestamp = request.getParameter("timestamp");
if (StringUtils.isAnyBlank(appId, sign, nonce, timestamp)) {
throw new ApiException("簽名參數(shù)不完整");
}
// 校驗時間戳
long currentTime = System.currentTimeMillis() / 1000;
if (Math.abs(currentTime - Long.parseLong(timestamp)) > EXPIRE_TIME) {
throw new ApiException("請求時間已過期");
}
// 重放校驗
String nonceKey = "nonce_cache:" + appId + ":" + nonce;
if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", EXPIRE_TIME, TimeUnit.SECONDS))) {
throw new ApiException("重復(fù)請求被拒絕");
}
// 簽名驗證
Map<String, String[]> parameterMap = request.getParameterMap();
String calculatedSign = SignUtils.calculateSign(parameterMap, getAppSecret(appId));
if (!sign.equalsIgnoreCase(calculatedSign)) {
throw new ApiException("簽名校驗失敗");
}
return true;
}
private String getAppSecret(String appId) {
// 實際項目中建議從數(shù)據(jù)庫或配置中心加載
return "secretFor_" + appId;
}
}步驟 3:簽名工具類封裝
public class SignUtils {
public static String calculateSign(Map<String, String[]> params, String appSecret) {
SortedMap<String, String> sortedParams = new TreeMap<>();
for (Map.Entry<String, String[]> entry : params.entrySet()) {
if ("sign".equalsIgnoreCase(entry.getKey())) continue;
sortedParams.put(entry.getKey(), entry.getValue()[0]);
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
sb.append("appSecret=").append(appSecret);
return DigestUtils.md5DigestAsHex(sb.toString().getBytes(StandardCharsets.UTF_8)).toUpperCase();
}
}步驟 4:配置攔截器生效
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private ApiSecurityInterceptor apiSecurityInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiSecurityInterceptor)
.addPathPatterns("/api/**");
}
}步驟 5:接口使用方式示例
@GetMapping("/api/secure/data")
@ApiSign
public Result<String> getSecureData() {
return Result.success("這是簽名驗證通過的數(shù)據(jù)");
}請求調(diào)用流程說明
- 調(diào)用方生成帶簽名的請求;
- 請求通過限流判斷 → 重放判斷 → 參數(shù)完整性校驗;
- 后端根據(jù)參數(shù)生成服務(wù)端簽名與客戶端簽名對比;
- 簽名一致放行,失敗則拒絕。
總結(jié)
本方案結(jié)合 Spring Boot 提供了一套輕量、可擴展的接口安全保護機制:
功能點 | 技術(shù)實現(xiàn) |
簽名校驗 | 自定義注解 + 攔截器 + 參數(shù)排序 + MD5 簽名 |
防重放 | Redis 緩存 |
限流 | Redis 計數(shù)器,單位時間請求次數(shù)限制 |
易用性 | 注解式接入,低侵入、接口層無需重復(fù)代碼 |
可擴展性 | 可集成 AES 加密、IP 白名單、權(quán)限控制等高級能力 |
適用于中臺接口、對接系統(tǒng)、開放平臺等場景??梢宰鳛榻y(tǒng)一接口安全網(wǎng)關(guān)的重要組成部分,也可獨立部署在 Spring Boot 服務(wù)中。




























