天天看點

最火的Spring Cloud Gateway 為經過身份驗證的使用者啟用速率限制實踐-spring cloud 入門教程

在本文中,您将學習如何使用 Spring Cloud Gateway 為經過身份驗證的使用者啟用速率限制。為什麼重要?API 網關是您的微服務系統的入口點。是以,您應該提供适當的安全級别。速率限制可以防止您的 API 遭受 DoS 攻擊并限制網絡抓取。

您可以使用 Spring Cloud Gateway 輕松配置速率限制。這個特性的基本介紹可以參考我的文章基于Redis做Spring Cloud Gateway 中的速率限制實踐-spring cloud 入門教程。同樣,今天我們也将使用 Redis 作為速率限制器的後端。此外,我們将配置 HTTP 基本身份驗證。當然,您可以提供一些更進階的身份驗證機制,例如 X509 證書或 OAuth2 登入。如果您考慮一下,請閱讀我的文章Spring Cloud Gateway OAuth2 with Keycloak。

1. 依賴

讓我們從依賴開始。由于我們将建立一個內建測試,我們需要一些額外的庫。首先,我們将使用 Testcontainers 庫。它允許我們在 JUnit 測試期間運作 Docker 容器。我們将使用它來運作 Redis 和一個模拟伺服器,它負責模拟下遊服務。當然,我們需要包含一個帶有 Spring Cloud Gateway 和 Spring Data Redis 的 starter。要實作 HTTP 基本身份驗證,我們還需要包含 Spring Security。這是 Maven 中所需依賴項的完整清單

pom.xml

<dependency>
   <groupId>org.springframework.cloud</groupId>
   <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>mockserver</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.mock-server</groupId>
   <artifactId>mockserver-client-java</artifactId>
   <scope>test</scope>
</dependency>           

2. 配置 HTTP 基本身份驗證

為了配置 HTTP 基本身份驗證,我們需要建立

@Configuration

帶有

@EnableWebFluxSecurity

. 這是因為 Spring Cloud Gateway 建立在 Spring WebFlux 和 Netty 之上。此外,我們将建立一組測試使用者

MapReactiveUserDetailsService

@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

   @Bean
   public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
      http.authorizeExchange(exchanges -> 
         exchanges.anyExchange().authenticated())
            .httpBasic();
      http.csrf().disable();
      return http.build();
   }

   @Bean
   public MapReactiveUserDetailsService users() {
      UserDetails user1 = User.builder()
            .username("user1")
            .password("{noop}1234")
            .roles("USER")
            .build();
      UserDetails user2 = User.builder()
            .username("user2")
            .password("{noop}1234")
            .roles("USER")
            .build();
      UserDetails user3 = User.builder()
            .username("user3")
            .password("{noop}1234")
            .roles("USER")
            .build();
      return new MapReactiveUserDetailsService(user1, user2, user3);
   }
}           

3.配置Spring Cloud Gateway Rate Limiter key

需要使用名為 的元件啟用請求速率限制器功能

GatewayFilter

。此過濾器采用可選 

keyResolver

 參數。該 

KeyResolver

 接口允許您建立可插拔政策,派生出限制請求的密鑰。在我們的例子中,它将是一個使用者登入。一旦使用者成功通過身份驗證,其登入資訊就會存儲在 Spring 中

SecurityContext

。為了檢索響應式應用程式的上下文,我們應該使用

ReactiveSecurityContextHolder

.

@Bean
KeyResolver authUserKeyResolver() {
   return exchange -> ReactiveSecurityContextHolder.getContext()
           .map(ctx -> ctx.getAuthentication()
              .getPrincipal().toString());
}           

4. 測試場景

在測試場景中,我們将模拟傳入流量。每個請求都需要有一個

Authorization

包含使用者憑據的标頭。單個使用者每分鐘可以發送 4 個請求。超過該限制後,Spring Cloud Gateway 将傳回 HTTP 代碼

HTTP 429 - Too Many Requests

。流量被尋址到下遊服務。是以,我們使用 Testcontainers 運作模拟伺服器。

最火的Spring Cloud Gateway 為經過身份驗證的使用者啟用速率限制實踐-spring cloud 入門教程

5. 測試 Spring Cloud Gateway 安全限速器

最後,我們可以進行測試實作。我将使用 JUnit4,因為我之前在示例存儲庫中的其他示例中使用過它。我們有三個用于速率限制器配置的參數:

replenishRate

burstCapacity

requestedTokens

。由于我們還允許每秒少于 1 個請求,是以我們需要為

burstCapacity

和設定正确的值

requestedTokens

。簡而言之,該

requestedTokens

屬性設定請求花費多少令牌。另一方面,

burstCapacity

屬性是允許使用者的最大請求數(或成本)。

在測試過程中,我們在

user1

user2

和之間随機設定使用者名

user3

。測試重複 20 次。

@SpringBootTest(webEnvironment = 
   SpringBootTest.WebEnvironment.DEFINED_PORT,
                properties = {"rateLimiter.secure=true"})
@RunWith(SpringRunner.class)
public class GatewaySecureRateLimiterTest {

   private static final Logger LOGGER = 
      LoggerFactory.getLogger(GatewaySecureRateLimiterTest.class);
   private Random random = new Random();

   @Rule
   public TestRule benchmarkRun = new BenchmarkRule();

   @ClassRule
   public static MockServerContainer mockServer = 
      new MockServerContainer();
   @ClassRule
   public static GenericContainer redis = 
      new GenericContainer("redis:5.0.6").withExposedPorts(6379);

   @Autowired
   TestRestTemplate template;

   @BeforeClass
   public static void init() {
      System.setProperty("spring.cloud.gateway.routes[0].id", "account-service");
      System.setProperty("spring.cloud.gateway.routes[0].uri", "http://" + mockServer.getHost() + ":" + mockServer.getServerPort());
      System.setProperty("spring.cloud.gateway.routes[0].predicates[0]", "Path=/account/**");
      System.setProperty("spring.cloud.gateway.routes[0].filters[0]", "RewritePath=/account/(?<path>.*), /$\\{path}");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].name", "RequestRateLimiter");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.replenishRate", "1");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.burstCapacity", "60");
      System.setProperty("spring.cloud.gateway.routes[0].filters[1].args.redis-rate-limiter.requestedTokens", "15");
      System.setProperty("spring.redis.host", redis.getHost());
      System.setProperty("spring.redis.port", "" + redis.getMappedPort(6379));
      new MockServerClient(mockServer.getContainerIpAddress(), mockServer.getServerPort())
            .when(HttpRequest.request()
                    .withPath("/1"))
            .respond(response()
                    .withBody("{\"id\":1,\"number\":\"1234567890\"}")
                    .withHeader("Content-Type", "application/json"));
   }

   @Test
   @BenchmarkOptions(warmupRounds = 0, concurrency = 1, benchmarkRounds = 20)
   public void testAccountService() {
      String username = "user" + (random.nextInt(3) + 1);
      HttpHeaders headers = createHttpHeaders(username,"1234");
      HttpEntity<String> entity = new HttpEntity<String>(headers);
      ResponseEntity<Account> r = template
         .exchange("/account/{id}", HttpMethod.GET, entity, Account.class, 1);
      LOGGER.info("Received({}): status->{}, payload->{}, remaining->{}",
            username, r.getStatusCodeValue(), r.getBody(), r.getHeaders().get("X-RateLimit-Remaining"));
    }

   private HttpHeaders createHttpHeaders(String user, String password) {
      String notEncoded = user + ":" + password;
      String encodedAuth = Base64.getEncoder().encodeToString(notEncoded.getBytes());
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON);
      headers.add("Authorization", "Basic " + encodedAuth);
      return headers;
   }

}           

讓我們運作測試。感謝

junit-benchmarks

庫,我們可以配置測試的輪數。每次我記錄來自網關的響應時,包括使用者名、HTTP 狀态、有效負載和

X-RateLimit-Remaining

顯示剩餘令牌數量的标頭。

結果如下。

最火的Spring Cloud Gateway 為經過身份驗證的使用者啟用速率限制實踐-spring cloud 入門教程

使用 Zuul、Ribbon、Feign、Eureka 和 Sleuth、Zipkin 建立簡單spring cloud微服務用例-spring cloud 入門教程

微服務內建SPRING CLOUD SLEUTH、ELK 和 ZIPKIN 進行監控-spring cloud 入門教程

使用Hystrix 、Feign 和 Ribbon建構微服務-spring cloud 入門教程

使用 Spring Boot Admin 監控微服務-spring cloud 入門教程

基于Redis做Spring Cloud Gateway 中的速率限制實踐-spring cloud 入門教程

內建SWAGGER2服務-spring cloud 入門教程

Hystrix 簡介-spring cloud 入門教程

Hystrix 原理深入分析-spring cloud 入門教程 

使用Apache Camel建構微服務-spring cloud 入門教程

內建 Kubernetes 來建構微服務-spring cloud 入門教程

內建SPRINGDOC OPENAPI 的微服務實踐-spring cloud 入門教程

SPRING CLOUD 微服務快速指南-spring cloud 入門教程

基于GraphQL的微服務實踐-spring cloud 入門教程

最火的Spring Cloud Gateway 為經過身份驗證的使用者啟用速率限制實踐-spring cloud 入門教程