Home > Back-end >  Spring Security context is always null in Spring Boot Cucumber Test
Spring Security context is always null in Spring Boot Cucumber Test

Time:12-31

I have created a service with Spring Boot and Reactor. I try to test with Cucumber. POM file (extract):

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.2</version>
  </parent>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-java</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-spring</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.cucumber</groupId>
      <artifactId>cucumber-junit-platform-engine</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-suite</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-console</artifactId>
      <scope>test</scope>
    </dependency>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-bom</artifactId>
        <version>7.10.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
      <dependency>
        <groupId>org.junit</groupId>
        <artifactId>junit-bom</artifactId>
        <version>5.9.1</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

My cucumber configuration looks like this:

@Suite
class CucumberTestRunner

@CucumberContextConfiguration
@ContextConfiguration(classes = [CucumberBootstrap::class])
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = [MyApplication::class])
class CucumberSpringContextConfig

@ComponentScan("com.mypackage")
@Configuration
class CucumberBootstrap

The step definition I'm working on is like this:

@ScenarioScope
class MyTestStepDefs(
  private val service: MyService
) {

  @Given("User is authenticated")
  fun userIsAuthenticated() {
    val auth = UsernamePasswordAuthenticationToken(
      User("username", "password", emptyList()),
      "credentials",
      emptyList<GrantedAuthority>()
    )

    val context = ReactiveSecurityContextHolder.getContext().block()

    //ReactiveSecurityContextHolder.getContext()
    //  .doOnNext { it.authentication = auth }
    //  .block()
  }
...
}

I would like to set the authenticated user into the security context. But unfortunately, the security context is always null.

I have also regular Springt Boot Tests running, where the security context is correctly returned. Here is an extract:

@SpringBootTest(webEnvironment = RANDOM_PORT)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyControllerTests @Autowired constructor(
  private val client: WebTestClient
) {

  @Test
  @WithMockUser
  fun `post instruction set`() {
    val context = ReactiveSecurityContextHolder.getContext().block()
    ...
  }
  ...
}

The service class is injected correctly into the step definitions class.

Does anyone have a hint what's missing in the configuration?

Thanks in advance for your help!

CodePudding user response:

The tests that work use the @WithMockUser annotation which work (as described here) because this enables the WithSecurityContextTestExecutionListener.

Unfortunately you can't use this with Cucumber, Cucumber doesn't have a single test method. But ultimately this listener sets the context using:

this.securityContextHolderStrategyConverter.convert(testContext).setContext(supplier.get())

This then invokes (via some indirection):

TestSecurityContextHolder.setContext(context)

Which you could do inside Cucumber too. Though you may want to review the implementation of the WithSecurityContextTestExecutionListener for what exactly you're trying to reproduce.

Alternatively you can do your test end-to-end including the authentication.

CodePudding user response:

Based on M.P. Korstanje's description, I implemented the step as follows:

  @Given("User is authenticated")
  fun userIsAuthenticated() {
    val auth = UsernamePasswordAuthenticationToken(
      User("username", "password", emptyList()),
      "credentials",
      emptyList<GrantedAuthority>()
    )

    val context = TestSecurityContextHolder.getContext()
    context.authentication = auth

    val newContext = ReactiveSecurityContextHolder.getContext().block()
    assertEquals(auth, newContext?.authentication, "Wrong authentication stored in security context")
  • Related