知識庫 / Spring / Spring Cloud RSS 訂閱

Spring Cloud Gateway 客户端 IP 速率限制

Spring Cloud
HongKong
5
11:48 AM · Dec 06 ,2025

1. 簡介

本快速教程將介紹如何根據客户端的實際 IP 地址限制傳入請求數量,應用於我們的 Spring Cloud Gateway。

簡而言之,我們將為路由配置 RequestRateLimiter 過濾器,然後 配置網關使用 IP 地址來限制來自不同客户端的請求

2. 路由配置

首先,我們需要配置 Spring Cloud Gateway 以對特定路由實施速率限制。為此,我們將使用經典的 令牌桶 速率限制器,該速率限制器由 spring-boot-starter-data-redis-reactive 實現。簡而言之,速率限制器創建一個與關聯鍵一起使用的桶,該鍵標識自身,並具有固定的初始容量,該容量隨着時間的推移而補充。然後,對於每個請求,速率限制器會檢查其關聯的桶,並在可能的情況下減少一個令牌。否則,它會拒絕傳入的請求。

由於我們正在處理分佈式系統,因此我們可能希望跟蹤所有傳入請求,無論它們來自應用程序的各個實例。因此,擁有分佈式緩存系統對於存儲桶的信息非常方便。在本例中,我們預先配置了一個 Redis 實例來模擬真實世界的應用程序。

接下來,我們將配置一個帶有速率限制器的路由。我們將監聽 /example 端點並將請求轉發到 http://example.org

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("requestratelimiter_route", p -> p
            .path("/example")
            .filters(f -> f.requestRateLimiter(r -> r.setRateLimiter(redisRateLimiter())))
            .uri("http://example.org"))
        .build();
}

上方,我們通過使用 .setRateLimiter() 方法配置了路由。特別是,我們通過 redisRatelimiter() 方法定義了 RedisRateLimiter Bean,用於管理我們的速率限制器的狀態:

@Bean
public RedisRateLimiter redisRateLimiter() {
    return new RedisRateLimiter(1, 1, 1);
}

為了説明,我們配置速率限制,將所有 replenishRateburstCapacityrequestedToken 屬性都設置為 1。 這樣就可以輕鬆地多次調用 /example 端點並獲得 HTTP 429 響應代碼。

3. KeyResolver Bean

為了正確工作,速率限制器必須通過鍵識別每個通過端點訪問的客户端。在背後,鍵確定速率限制器將用於為每個請求消耗令牌的桶。因此,我們希望鍵對於每個客户端都是唯一的。在這種情況下,我們將使用客户端的 IP 地址來監控他們的請求並限制他們發送的請求數量。

因此,我們之前配置的 RequestRateLimiter 將使用一個 KeyResolver Bean,該 Bean 允許可插拔的策略從請求中派生鍵,用於限制請求。這意味着 我們可以配置如何從每個請求中提取鍵

4. 客户端 IP 地址在 KeyResolver

目前,此接口沒有默認實現,因此我們必須定義一個,同時考慮到我們希望獲取客户端的 IP 地址:

@Component
public class SimpleClientAddressResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        return Optional.ofNullable(exchange.getRequest().getRemoteAddress())
            .map(InetSocketAddress::getAddress)
            .map(InetAddress::getHostAddress)
            .map(Mono::just)
            .orElse(Mono.empty());
    }
}

我們正在使用 ServerWebExchange 對象來提取客户端的 IP 地址。如果無法獲取 IP 地址,我們將返回 Mono.empty() 以向速率限制器發出信號並默認拒絕請求。但是,我們可以配置速率限制器,允許在 KeyResolver 返回空鍵時通過設置 .setDenyEmptyKey()false 接收請求。 此外,我們還可以為每個不同的路由提供不同的 KeyResolver,通過向 .setKeyResolver() 方法提供自定義的 KeyResolver 實現來實現:

builder.routes()
    .route("ipaddress_route", p -> p
        .path("/example2")
        .filters(f -> f.requestRateLimiter(r -> r.setRateLimiter(redisRateLimiter())
            .setDenyEmptyKey(false)
            .setKeyResolver(new SimpleClientAddressResolver())))
        .uri("http://example.org"))
.build();

4.1. 代理服務器下源IP地址

當Spring Cloud Gateway直接監聽客户端請求時,之前定義的實現方案有效。但是,如果將應用程序部署在代理服務器後面,所有主機地址將相同。因此,速率限制器會將所有請求都視為來自同一客户端,並限制它可以處理的請求數量。

為了解決這個問題,我們依賴 X-Forwarded-For 標頭來識別通過代理服務器連接的客户端的源IP地址。例如,讓我們配置 KeyResolver 以讀取源IP地址:

@Primary
@Component
public class ProxyClientAddressResolver implements KeyResolver {
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        XForwardedRemoteAddressResolver resolver = XForwardedRemoteAddressResolver.maxTrustedIndex(1);
        InetSocketAddress inetSocketAddress = resolver.resolve(exchange);
        return Mono.just(inetSocketAddress.getAddress().getHostAddress());
    }
}

我們將值 1 傳遞給 maxTrustedIndex(),假設我們只有一個代理服務器。否則,該值必須相應地設置。此外,我們使用 KeyResolver 註解了 @Primary 以使其優先級高於以前的實現。

5. 結論

在本文中,我們配置了一個基於客户端 IP 地址的 API 速率限制器。首先,我們配置了一個帶有令牌桶速率限制器的路由。然後,我們探討了 KeyResolver 如何識別每個請求所使用的桶。最後,我們探討了在直接訪問我們的 API 或其部署在代理服務器後,通過 KeyResolver 將客户端 IP 地址分配的策略。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.