spring-security: SecurityMockMvcConfigurer regression in Spring Boot 2.2.1

Summary

SecurityMockMvcConfigurer$DelegateFilter is not null-safe, if no springSecurityFilterChain is available.

Actual Behavior

java.lang.NullPointerException
	at org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurer$DelegateFilter.doFilter(SecurityMockMvcConfigurer.java:132)
	at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
	at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:183)
	at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.performRequest(MockMvcRequestSenderImpl.java:219)
	at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.sendRequest(MockMvcRequestSenderImpl.java:448)
	at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.put(MockMvcRequestSenderImpl.java:505)
	at io.restassured.module.mockmvc.internal.MockMvcRequestSenderImpl.put(MockMvcRequestSenderImpl.java:101)
	at com.example.demo.rest.DemoResourceTest.testEcho(DemoResourceTest.java:36)

Expected Behavior

No exception should be thrown even when no springSecurityFilterChain is available.

Configuration

Our application uses spring security, and org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers is also in the test classpath. In a REST endpoint test (Mockito+RestAssured based, not full integration test, and security is not of interest), springSecurityFilterChain is not available.

Version

The issue exists in spring-security-test 2.2.x. In spring-security-test 2.0.x, it works fine.

Related to issue https://github.com/spring-projects/spring-security/issues/7688

Sample

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>security-mock-mvc-config-regression</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>Sample for SecurityMockMvcConfigurer regression</name>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>spring-mock-mvc</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
package com.example.demo.rest;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoResource {

    @PutMapping(value = "/echo")
    public ResponseEntity<String> echo(@RequestBody String message) {
        return ResponseEntity.ok(message.toLowerCase());
    }

}
package com.example.demo.rest;

import static io.restassured.module.mockmvc.RestAssuredMockMvc.given;
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;

import io.restassured.module.mockmvc.RestAssuredMockMvc;

public class DemoResourceTest {

    @InjectMocks
    private DemoResource target;

    @BeforeEach
    public void beforeEach() {
        MockitoAnnotations.initMocks(this);
        RestAssuredMockMvc.standaloneSetup(target);
    }

    @Test
    public void testEcho() {
        String input = "TEST";

        String message = given()
            .body(input)
        .when()
            .put("/echo")
        .then()
            .statusCode(HttpStatus.OK.value())
            .extract()
            .response().asString();

        assertThat(message).isEqualTo(input.toLowerCase());
    }

}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 16 (10 by maintainers)

Commits related to this issue

Most upvoted comments

Have you already tried the RestAssured support for turning off security?

For example. you could do something like:

RestAssuredMockMvcConfig noSecurity() {
        return config().mockMvcConfig(mockMvcConfig()
                .dontAutomaticallyApplySpringSecurityMockMvcConfigurer());
}

And then in your test doing:

given().config(noSecurity())
    .body(...

The RestAssured folks may have a different opinion on the preferred way to shut off security for a request.

I prefer the above solution since Spring Security’s test support probably shouldn’t be added at all if Spring Security isn’t configured. The SecurityMockMvcConfigurer#beforeMockMvcCreated implementation is written with this in mind.

However, you could also consider doing:

RestAssuredMockMvc.standaloneSetup(target,
        springSecurity((request, response, chain) -> chain.doFilter(request, response)));

Which would replace the spring security filter chain for all tests in the class.

Would one of these address your concern?

Thanks @jzheaux - I figured out eventually the way to set up a global config which worked for me in the CDC case:

RestAssuredMockMvc.config = new RestAssuredMockMvcConfig().mockMvcConfig(
        mockMvcConfig().dontAutomaticallyApplySpringSecurityMockMvcConfigurer());

can I pick this up?