1
votes

We have library which provides a custom filter for our requests, which we use to write information on the request to our logs. We previously only needed servlet support, but now need to add webflux support. I've created a new WebFilter, which works as expected.

However, trying to test this new filter has been difficult. We're relying on the principal in the request for user information for the log, but during the test, it is always null. I've tried many things, including following the examples here: https://docs.spring.io/spring-security/site/docs/5.1.0.RELEASE/reference/html/test-webflux.html

My Filter:

package com.project.utils.security.request.logging.config.autoconfig;

import com.project.utils.logging.LoggingUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

public class ReactiveRequestLoggingFilter implements WebFilter {
  private static final Logger logger = LoggerFactory.getLogger(ReactiveRequestLoggingFilter.class);

  @Override
  public Mono<Void> filter(ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
    return serverWebExchange
        .getPrincipal()
        .flatMap(
            principal -> {
              LoggingUtil loggingUtil =
                  new LoggingUtil(serverWebExchange.getRequest(), principal.getName());
              loggingUtil.setEvent("Http Request");
              logger.info(loggingUtil.toString());
              return webFilterChain.filter(serverWebExchange);
            });
  }
}

My Test:

package com.project.utils.security.request.logging.config.autoconfig;

import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockUser;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
import com.project.utils.security.request.logging.config.testcontrollers.ReactiveTestController;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = {ReactiveTestController.class})
class ReactiveRequestLoggingFilterTest {
  @Autowired ReactiveTestController controller;
  ReactiveRequestLoggingFilter filter = new ReactiveRequestLoggingFilter();

  @Test
  void test() {
    WebTestClient client =
        WebTestClient.bindToController(controller)
            .webFilter(filter)
            .apply(springSecurity())
            .configureClient()
            .build()
            .mutateWith(mockUser("User"));
    client
        .get()
        .uri("/test")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo("GET");
  }
}

The controller I'm using for the test:

package com.project.utils.security.request.logging.config.testcontrollers;

import com.project.utils.security.request.logging.config.annotations.EnableReactiveRequestLogging;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@RestController
@RequestMapping("/test")
@EnableReactiveRequestLogging
public class ReactiveTestController {
  @GetMapping
  Mono<ResponseEntity<String>> get() {
    return Mono.just(ResponseEntity.ok("GET"));
  }
}

We are using Spring Boot 2.1, which uses Spring Security 5.1. Like I said, the actual functionality works, but we need to get tests written for it, and I can't figure out how to get the principal into the request.

Thanks!

1
And you need to be authenticated to access /test? Otherwise, Spring Security is not applied to that specific endpoint, then there is no concept of a principal when making a request to that endpoint. - Dirk Deyne
I need it to be authenticated as the filter relies on the principal being present. - Steve

1 Answers

0
votes

Okay, found a working solution from this answer: https://stackoverflow.com/a/55426458/1370823

My final test looked like:

package com.project.utils.security.request.logging.config.autoconfig;

import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
import com.project.utils.security.request.logging.config.testcontrollers.ReactiveTestController;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {ReactiveTestController.class, ReactiveRequestLoggingFilter.class})
@WebFluxTest(controllers = {ReactiveTestController.class})
@WithMockUser(value = "Test User", username = "TestUser")
class ReactiveRequestLoggingFilterTest {
  @Autowired ReactiveTestController controller;
  @Autowired ReactiveRequestLoggingFilter filter;

  @Test
  void test() {
    WebTestClient client =
        WebTestClient.bindToController(controller)
            .webFilter(new SecurityContextServerWebExchangeWebFilter())
            .webFilter(filter)
            .apply(springSecurity())
            .build();
    client
        .get()
        .uri("/test")
        .exchange()
        .expectStatus()
        .isOk()
        .expectBody(String.class)
        .isEqualTo("GET");
  }
}