硬核實戰(zhàn)!分布式系統(tǒng)接口安全三板斧:限流、防重放、簽名驗證全攻略
在更廣泛的分布式系統(tǒng)場景中,單一的防護措施遠遠不夠。系統(tǒng)需要面對復(fù)雜的攻擊手法,例如 大規(guī)模惡意請求、請求重放以及參數(shù)篡改 等。本文將聚焦于通用接口的三大安全防護手段,并結(jié)合工程實踐給出詳細實現(xiàn)方案:
- 限流機制:抵御突發(fā)流量與惡意刷接口,保證系統(tǒng)穩(wěn)定性
- 防重放策略:避免重復(fù)請求造成業(yè)務(wù)風(fēng)險
- 簽名校驗:防止請求數(shù)據(jù)被惡意篡改
通過這一系列措施,分布式系統(tǒng)不僅能在高并發(fā)場景下保持服務(wù)可用,還能有效阻斷潛在的攻擊通道。
限流機制:系統(tǒng)穩(wěn)定性的第一道防線
限流的核心目標(biāo)
限流的目的在于防止資源被過度消耗,從而避免 DoS/DDoS 攻擊和瞬時高并發(fā)帶來的雪崩效應(yīng)。對于分布式架構(gòu)來說,限流不僅要在 網(wǎng)關(guān)層 生效,還需要深入到 各微服務(wù)節(jié)點,形成多層級的保護體系。
常見的限流算法
在工程實踐中,通常會使用以下幾種限流算法:
算法 | 核心機制 | 優(yōu)點 | 缺點 | 適用場景 |
固定窗口 | 固定時間段內(nèi)計數(shù) | 實現(xiàn)簡單,高效 | 臨界點突刺問題 | 非關(guān)鍵接口 |
滑動窗口 | 滑動區(qū)間內(nèi)實時統(tǒng)計 | 精度高,平滑臨界 | 實現(xiàn)稍復(fù)雜 | API 網(wǎng)關(guān)、分布式限流 |
漏桶算法 | 恒定速率處理請求 | 流量絕對平滑 | 無法應(yīng)對突發(fā)流量 | 流量整形、數(shù)據(jù)庫保護 |
令牌桶算法 | 令牌發(fā)放 + 消耗機制 | 支持突發(fā),靈活性高 | 實現(xiàn)復(fù)雜 | 微服務(wù)、方法級限流 |
在本文的實現(xiàn)方案中,我們采用 Redis + 滑動窗口,以滿足分布式環(huán)境下的多節(jié)點限流需求。
Redis 滑動窗口限流實現(xiàn)
Redis 的 有序集合(ZSet) 特性使其非常適合實現(xiàn)滑動窗口算法。核心流程如下:
- 請求到達時,計算窗口起始時間
T - W。 - 使用
ZREMRANGEBYSCORE移除過期時間戳。 - 使用
ZCOUNT統(tǒng)計當(dāng)前窗口內(nèi)的請求數(shù)量。 - 若超出閾值,拒絕請求;否則寫入當(dāng)前時間戳。
- 使用 Lua 腳本封裝,保證原子性。
對應(yīng)實現(xiàn)如下:
package com.icoderoad.security.limiter;
import java.util.LinkedList;
import java.util.Queue;
public class SlidingWindowRateLimiter {
private final int limit; // 限流閾值
private final long windowSize; // 滑動窗口大小
private final Queue<Long> requestTimestamps;
public SlidingWindowRateLimiter(int limit, long windowSize) {
this.limit = limit;
this.windowSize = windowSize;
this.requestTimestamps = new LinkedList<>();
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
while (!requestTimestamps.isEmpty() && now - requestTimestamps.peek() > windowSize) {
requestTimestamps.poll();
}
if (requestTimestamps.size() < limit) {
requestTimestamps.offer(now);
return true;
}
return false;
}
}網(wǎng)關(guān)層限流
在 API 網(wǎng)關(guān)層 進行全局流量控制,可有效抵御惡意流量對下游服務(wù)的沖擊。例如設(shè)置全局 QPS = 100,000,作為兜底防護。
// 路徑:/src/main/java/com/icoderoad/gateway/filter/GlobalRateLimitFilter.java
package com.icoderoad.gateway.filter;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.LinkedList;
import java.util.Queue;
/**
* 全局限流過濾器(基于滑動窗口)
* 控制 API 網(wǎng)關(guān)層整體流量,避免惡意請求沖擊下游服務(wù)
*/
@Component
public class GlobalRateLimitFilter implements GlobalFilter, Ordered {
// 每秒最大請求數(shù)(例如全局 QPS = 100,000)
private static final int LIMIT = 100000;
// 窗口大?。ê撩耄? private static final long WINDOW_SIZE = 1000;
// 請求時間戳隊列
private final Queue<Long> requestTimestamps = new LinkedList<>();
/**
* 滑動窗口限流核心邏輯
*/
private synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 移除過期的請求時間
while (!requestTimestamps.isEmpty() && now - requestTimestamps.peek() > WINDOW_SIZE) {
requestTimestamps.poll();
}
if (requestTimestamps.size() < LIMIT) {
requestTimestamps.offer(now);
return true;
}
return false;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
if (!tryAcquire()) {
// 超過限流閾值,返回 429
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
// 放行
return chain.filter(exchange);
}
@Override
public int getOrder() {
// 優(yōu)先級最高,先執(zhí)行限流判斷
return -1;
}
}IP 限流
對攻擊者常用的 撞庫、短信轟炸 等行為,可以通過 單 IP 限制請求頻率 來攔截。若單 IP 在固定周期內(nèi)多次觸發(fā)限流規(guī)則,可直接拉黑。
// 路徑:/src/main/java/com/icoderoad/gateway/filter/IpRateLimitFilter.java
package com.icoderoad.gateway.filter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.concurrent.TimeUnit;
/**
* IP 限流過濾器(基于 Redis + 黑名單)
* 攔截異常頻繁的請求,例如撞庫、短信轟炸
*/
@Component
public class IpRateLimitFilter implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 配置參數(shù)
private static final int LIMIT = 50; // 單 IP 在周期內(nèi)最大請求數(shù)
private static final long PERIOD = 60; // 限流統(tǒng)計周期(秒)
private static final int BLACKLIST_THRESHOLD = 5; // 觸發(fā)限流多少次后拉黑
private static final long BLACKLIST_TIME = 3600; // 黑名單時長(秒)
@Override
public Mono<Void> filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) {
String ip = getClientIp(exchange);
String blacklistKey = "ip:blacklist:" + ip;
// 檢查是否在黑名單
if (Boolean.TRUE.equals(redisTemplate.hasKey(blacklistKey))) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
String counterKey = "ip:counter:" + ip;
Long count = redisTemplate.opsForValue().increment(counterKey);
if (count == 1) {
// 設(shè)置過期時間
redisTemplate.expire(counterKey, PERIOD, TimeUnit.SECONDS);
}
if (count != null && count > LIMIT) {
// 記錄觸發(fā)次數(shù)
String triggerKey = "ip:trigger:" + ip;
Long triggerCount = redisTemplate.opsForValue().increment(triggerKey);
if (triggerCount == 1) {
redisTemplate.expire(triggerKey, BLACKLIST_TIME, TimeUnit.SECONDS);
}
// 如果觸發(fā)次數(shù)超過閾值,拉黑
if (triggerCount != null && triggerCount >= BLACKLIST_THRESHOLD) {
redisTemplate.opsForValue().set(blacklistKey, "1", BLACKLIST_TIME, TimeUnit.SECONDS);
}
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 獲取客戶端真實 IP
*/
private String getClientIp(ServerWebExchange exchange) {
String ip = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
if (ip == null || ip.isEmpty()) {
ip = exchange.getRequest().getRemoteAddress() != null ?
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
} else {
// 取第一個 IP
ip = ip.split(",")[0];
}
return ip;
}
@Override
public int getOrder() {
// 高優(yōu)先級執(zhí)行,避免攻擊流量進入業(yè)務(wù)邏輯
return -2;
}
}微服務(wù)層限流
服務(wù)級別的限流側(cè)重 自治能力,例如對某個接口設(shè)置 QPS = 200,以避免單服務(wù)過載影響全局。
// 路徑:/src/main/java/com/icoderoad/service/filter/ResourceLimiterFilter.java
package com.icoderoad.service.filter;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 微服務(wù)級別的接口限流過濾器
* 支持從 Nacos 動態(tài)加載配置,實現(xiàn)服務(wù)自治
*/
@Component
public class ResourceLimiterFilter extends OncePerRequestFilter {
// Nacos 動態(tài)配置:例如 {"api:/order/create":200,"api:/user/login":100}
@NacosValue(value = "${service.limiter.config:{}}", autoRefreshed = true)
private String limiterConfig;
// 保存各接口的限流器
private final Map<String, SimpleRateLimiter> limiters = new ConcurrentHashMap<>();
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String uri = request.getRequestURI();
int qpsLimit = getLimit(uri);
if (qpsLimit > 0) {
SimpleRateLimiter limiter = limiters.computeIfAbsent(uri, k -> new SimpleRateLimiter(qpsLimit));
if (!limiter.tryAcquire()) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Too Many Requests for API: " + uri);
return;
}
}
filterChain.doFilter(request, response);
}
/**
* 根據(jù) URI 獲取限流閾值(從 Nacos 動態(tài)配置加載)
*/
private int getLimit(String uri) {
try {
// 假設(shè)配置格式是 JSON:{"api:/order/create":200,"api:/user/login":100}
Map<String, Object> configMap = new com.fasterxml.jackson.databind.ObjectMapper()
.readValue(limiterConfig, Map.class);
Object limit = configMap.get("api:" + uri);
return limit != null ? Integer.parseInt(limit.toString()) : 0;
} catch (Exception e) {
return 0;
}
}
/**
* 簡單的滑動窗口限流器(服務(wù)級別)
*/
static class SimpleRateLimiter {
private final int limit;
private final long windowSize = 1000L; // 1秒
private long windowStart = System.currentTimeMillis();
private int count = 0;
public SimpleRateLimiter(int limit) {
this.limit = limit;
}
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
if (now - windowStart > windowSize) {
windowStart = now;
count = 0;
}
if (count < limit) {
count++;
return true;
}
return false;
}
}
}防重放機制:阻斷重復(fù)提交的風(fēng)險
重放攻擊的典型手法是 攔截合法請求并重復(fù)發(fā)送,例如重復(fù)支付、重復(fù)下單。 防護策略常用 Nonce + Timestamp,確保請求 唯一且有時效性。
- Nonce(隨機數(shù)):保證請求唯一。
- Timestamp(時間戳):限制請求有效期,例如 5 分鐘內(nèi)有效。
- RequestId(唯一標(biāo)識):防止同一請求多次提交。
簽名校驗:確保請求未被篡改
簽名驗證的核心思路是:請求參數(shù) + 隨機數(shù) + 請求方法 + 加密算法 生成簽名,服務(wù)端對其進行解密和比對,保證數(shù)據(jù)完整性。
簽名生成流程
- 生成隨機數(shù)
requestId - 使用公鑰加密生成
idSign - 隨機抽取部分字符作為
idSecretIndex - 拼接 URL / 方法 / requestId 等數(shù)據(jù),使用對稱密鑰生成
signature - 客戶端請求時在 Header 攜帶:
X-Request-ID: idSign
X-Request-Index: idSecretIndex
X-Signature: signature前端簽名工具(Vue + Axios 示例)
// 路徑:/src/frontend/utils/signature.js
import CryptoJS from 'crypto-js'
import JSEncrypt from 'jsencrypt'
export function generateSignature(url, method, requestId, secretKey) {
const signContent = [`url=${url}`, `method=${method}`, `requestId=${requestId}`].join('&')
return CryptoJS.HmacSHA256(signContent, secretKey).toString(CryptoJS.enc.Hex)
}
export function encryptRequestId(requestId, publicKey) {
const encryptor = new JSEncrypt()
encryptor.setPublicKey(publicKey)
return encryptor.encrypt(requestId)
}總結(jié)
在分布式系統(tǒng)中,接口安全防護不是可選項,而是生死攸關(guān)的必備能力。
- 限流 保護系統(tǒng)不被突發(fā)流量擊垮,確保整體穩(wěn)定。
- 防重放 避免業(yè)務(wù)重復(fù)執(zhí)行,保證交易安全。
- 簽名校驗 阻止數(shù)據(jù)被篡改,提升接口的可信度。
這三大措施相輔相成:
- 限流提供流量層面的穩(wěn)定性;
- 防重放確保請求唯一性;
- 簽名校驗保障數(shù)據(jù)完整性。
如果把分布式系統(tǒng)比作一座城市,那么限流就是交通燈,防重放是門禁系統(tǒng),而簽名驗證則是身份證認(rèn)證。三者協(xié)同,才能讓城市安全高效地運行。
在后續(xù)實踐中,還可以結(jié)合 零信任架構(gòu)、API 網(wǎng)關(guān)安全插件、AI 風(fēng)控模型 等手段,構(gòu)建更加全面的防御體系。



























