English 中文(简体)
如果允许公众终端点的请求通过,我如何写上“春云反应的安普森网关”的测试单位?
原标题:How do I write a unit test for a Spring Cloud Reactive API Gateway s Custom Filter to test if it allows requests to public endpoints to pass?

我正试图在微观服务结构中为春季云云反应的AccountFilter进行单位测试。 关口正在使用网络F奢侈品,而如果最终点是公开的或安全的,则负责检查。 如果该请求是公开的终点,则允许通过。 然而,如果是安全的终点,则在允许请求通过之前,对一名JWT负责人进行严格审查。

我尝试了多种办法和执行,但我无法通过我的单位测试。 我怀疑这个问题可能与我的执行有关,但我不相信。

如果任何人能够就如何在微观服务结构中使用网上F奢侈品,正确写这一天云反应性预报网关的试验提供一些指导或想法,我将非常赞赏。

<>GatewayApplication.java:

package gateway;

import gateway.filters.*; // simplified the import all the filter once (AuthAccountFilter included)
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.server.handler.DefaultWebFilterChain;

@SpringBootApplication(exclude = { ErrorMvcAutoConfiguration.class })
@EnableEurekaClient
@CrossOrigin(origins = "*", allowedHeaders = "*")
@EnableDiscoveryClient
@EnableHystrix
public class GatewayApplication implements CommandLineRunner {

  public static void main(String[] args) {
    SpringApplication.run(GatewayApplication.class, args);
  }

  @Bean
  public RouteLocator routeLocator(RouteLocatorBuilder rlb, AuthAccountFilter authAccountFilter) {
    return rlb
        .routes()
        .route(p -> p
            .path("/my-service/**")
            .filters(f -> f
                .rewritePath("/my-service/(?<segment>.*)", "/$\{segment}")
                .filter(authAccountFilter.apply(new AuthAccountFilter.Config())))
            .uri("lb://MY-SERVICE"))
        .build();
  }

  @Override
  public void run(String... args) throws Exception {
    System.out.println("... My-Service is UP -- READY TO GO!");
  }
}

<>AuthAccountFilter.java:

package gateway.filters;

import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.shaded.json.JSONObject;
import com.nimbusds.jwt.JWTClaimsSet;
import org.apache.http.entity.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;

@Component
public class AuthAccountFilter extends AbstractGatewayFilterFactory<AuthAccountFilter.Config> {

  private Logger LOGGER = LoggerFactory.getLogger(AuthAccountFilter.class);

  @Autowired
  WebClient.Builder webClientBuilder;

  @Override
  public Class<Config> getConfigClass() {
    return Config.class;
  }

  public static class Config {
    // empty class as I don t need any particular configuration
  }

  @Override
  public GatewayFilter apply(Config config) {

    return (exchange, chain) -> {
      String endpoint = exchange.getRequest().getPath().toString();

      LOGGER.trace("Gateway filter for endpoint : " + endpoint);

      LOGGER.info("Checking permission for endpoint : " + endpoint);
      if (exchange.getRequest().getPath().toString().contains("auth") ||
          exchange.getRequest().getPath().toString().contains("otp") ||
          exchange.getRequest().getPath().toString().toLowerCase().contains("reset-password")) {
        LOGGER.info("Public endpoint, aborting filter");
        Mono<Void> filter = chain.filter(exchange);
        System.err.println(filter == null);
        return filter;
      }

    };
  }

}

<>AuthAccountFilter Test.java:

package gateway.filters;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpResponse;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Mono;

import java.util.Arrays;

import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
class AuthAccountFilterTest {

  private GatewayFilterChain filterChain = mock(GatewayFilterChain.class);

  @Test
  void testPublicEndpoint() {
    String baseUrl = "http://localhost:9090/my-service/";

    // Create a mock request and response
    MockServerHttpRequest request = MockServerHttpRequest.get(baseUrl + "auth").build();
    MockServerHttpResponse response = new MockServerHttpResponse();

    // Create an instance of your AuthFilter and any dependencies it has
    AuthAccountFilter filter = new AuthAccountFilter();

    WebFilterChain chain = (exchange, filterChain) -> {
      // Set the Config instance on the Exchange object
      AuthAccountFilter.Config config = new AuthAccountFilter.Config();
      exchange.getAttributes().put("config", config);

      // Call the apply method of the AuthFilter, passing in the Config instance
      return filter.apply(config);
    };
  }
}

事先感谢你提供任何帮助。

问题回答

也许测试网关过滤器的最佳方法是使用<条码>Web 测试Client进行集成春季波束测试。 这将使最终到终申请处理得到验证,并确保所有组合都得到正确界定。

In order to do it you would need to make your routes testable by extracting downstream service URI into configuration properties.

@Bean
public RouteLocator routeLocator(RouteLocatorBuilder rlb, AuthAccountFilter authAccountFilter) {
    return rlb
            .routes()
            .route(p -> p.path("/my-service/*")
                    .filters(f -> f
                            .rewritePath("/my-service/(?<segment>.*)", "/$\{segment}")
                            .filter(authAccountFilter.apply(new AuthAccountFilter.Config())))
                    .uri(properties.getServiceUri()))
            .build();
}

此外,你还可以使用电线磁盘对下游服务进行模拟,并正确确定路线。

SpringBootTest(webEnvironment = RANDOM_PORT)
@AutoConfigureWireMock(port = 0) // random port
@AutoConfigureWebTestClient
class GatewayConfigurationTest {

    @Autowired
    private GatewayProperties gatewayProperties;

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void verifyAuthRequest() {
        // mock downstream service
        stubFor(get(urlPathMatching("/auth"))
                .willReturn(aResponse()
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .withStatus(200)
                )
        );

        // make request to gateway
        webTestClient
                .get()
                .uri("/my-service/auth")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                .expectStatus().isOk();

        verify(1, getRequestedFor(urlEqualTo("/auth")));
    }

    @TestConfiguration
    static class TestGatewayConfiguration {

        public TestGatewayConfiguration(
                @Value("${wiremock.server.port}") int wireMockPort,
                GatewayProperties properties) {
            properties.setServiceUri("http://localhost:" + wireMockPort);
        }
    }
}

这一测试依据的是<代码>。 AutoConfigureWireMock, 且您需要将测试依赖性添加到org. mersframework.cloud: mers-cloud- Contracting-wiremock。 作为一种替代办法,你可以直接依赖电线磁,并明确予以启动。

您仍然能够使用模拟要求单独测试你的过滤器,但在你的情况下,它很少提供现场检测。

@Test
void filterTest() {
    var filterFactory = new AuthAccountFilter();

    MockServerHttpRequest request = MockServerHttpRequest.get("/my-service/auth").build();
    MockServerWebExchange exchange = MockServerWebExchange.from(request);

    var filter = filterFactory.apply(new AuthAccountFilter.Config());

    GatewayFilterChain filterChain = mock(GatewayFilterChain.class);
    ArgumentCaptor<ServerWebExchange> captor = ArgumentCaptor.forClass(ServerWebExchange.class);
    when(filterChain.filter(captor.capture())).thenReturn(Mono.empty());

    StepVerifier.create(filter.filter(exchange, filterChain))
            .verifyComplete();

    var resultExchange = captor.getValue();
    // verify result exchange
}

页: 1

Take a look at Spring Security that would allow you to define the same rules using SecurityWebFilterChain htt页: 1://docs.spring.io/spring-security/reference/reactive/configuration/webflux.html.

@Bean
public SecurityWebFilterChain apiSecurity(ServerHttpSecurity http) {
    http.authorizeExchange()
            .pathMatchers("/my-service/auth").permitAll()
            .pathMatchers("/my-service/otp").permitAll()
            .pathMatchers("/my-service/**/reset-password").permitAll()
            .anyExchange().authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt();
    return http.build();
}

我之所以谈这个问题,是因为我在测试网上豪华(SpringBoot)申请时面临同样的挑战。

I figured out testing that kind of scenario by using the Spring Cloud Gateway properties:

  1. Mock a backend response to mimic the service protected by your filter.
    private static MockWebServer mockExternalBackEnd;
    private static ObjectMapper objectMapper;
    @Autowired
    WebTestClient webTestClient;
    MyFilter myFilter;

    @Builder
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    private static class StubResponse {
        private String status;
    }

    @BeforeAll
    static void setUp() throws IOException {
        objectMapper = createObjectMapper();
        mockExternalBackEnd = new MockWebServer();
        mockExternalBackEnd.start();
    }

    @AfterAll
    static void tearDown() throws IOException {
        mockExternalBackEnd.shutdown();
    }

    @BeforeEach
    public void init() throws JsonProcessingException {
        this.myFilter = new MyFilter();
        // External service to be redirected answers "OK"
        mockExternalBackEnd.enqueue(new MockResponse()
                .setResponseCode(HttpStatus.OK.value())
                .setBody(objectMapper.writeValueAsString(StubResponse.builder().status(HttpStatus.OK.name()).build()))
                .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE));
    }

  1. Configure a route to your protected service (the mocked backend above).
@DynamicPropertySource
    static void updateWebClientConfig(DynamicPropertyRegistry dynamicPropertyRegistry) {
        dynamicPropertyRegistry.add("spring.cloud.gateway.routes[0].id", () -> "mock");
        dynamicPropertyRegistry.add("spring.cloud.gateway.routes[0].predicates[0]", () -> "Path=/services/mock/**");
        dynamicPropertyRegistry.add("spring.cloud.gateway.routes[0].uri",
                () -> String.format("http://localhost:%s", mockExternalBackEnd.getPort()));
    }
  1. Use WebTestClient to call your protected service, passing through the filter(s).
    @Test
    void testAccessServiceUsingTokenIsOk() {
        webTestClient
                .get()
                .uri("/services/mock/stub")
                .exchange()
                .expectStatus().isForbidden();
    }

为了做到这一点,你的安保结构应当有一条“服务/**”道路,通过认证来保护“服务/**”道路,并登记你的过滤器:

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfiguration {

...

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        // Note: Omitted some configurations
        http
            .csrf()
                .disable()
            .addFilterAt(new MyFilter(), SecurityWebFiltersOrder.HTTP_BASIC)
            .authenticationManager(reactiveAuthenticationManager())
        .and()
            .authorizeExchange()
            .pathMatchers("/").permitAll()
            .pathMatchers("/services/**").authenticated()
        return http.build();
    }

...

}

然后,你可以断言,是否与服务机构联系,并回答预期的答复:

StubResponse expectedResponse = StubResponse.builder().status(HttpStatus.OK.name()).build();

StubResponse externalServiceResponse = webTestClient
                .get()
                .uri("/services/mock/stub")
                .header("Authorization", "Bearer ...")
                .exchange()
                .expectStatus().isOk()
                .expectBody(StubResponse.class)
                .returnResult()
                .getResponseBody();
        assertThat(externalServiceResponse).isEqualTo(expectedResponse);




相关问题
Run JUnit automatically when building Eclipse project

I want to run my unit tests automatically when I save my Eclipse project. The project is built automatically whenever I save a file, so I think this should be possible in some way. How do I do it? Is ...

Embedded jetty ServletTester serving single static file

I m unit testing with jetty and I want to serve not only my servlet under test but a static page as well. The static page is needed by my application. I m initializing jetty like this tester = new ...

Applying one test to two separate classes

I have two different classes that share a common interface. Although the functionality is the same they work very differently internally. So naturally I want to test them both. The best example I ...

热门标签