Skip to content

Fails to work with Spring Security after upgrading to Spring Boot 2.5 #707

@ieu

Description

@ieu

problem-spring-web fails to work with Spring Security after upgrading to Spring Boot 2.5

Description

After introducing spring-security as dependency:

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

problem-spring-web will no longer work.

Expected Behavior

Work as expected.

For example, normally with problem-spring-web, a validation failure message looks like below:

{
    "title": "Bad Request",
    "status": 400,
    "detail": "Required request parameter 'message' for method parameter type String is not present"
}

Actual Behavior

  1. Authentication failure will cause empty response with status 200.

  2. Application will simply respond as Spring Boot's default beheavior will do on other expections thrown:

{
    "timestamp": "2021-11-10T00:00:00.000+00:00",
    "status": 400,
    "error": "Bad Request",
    "path": "/echo"
}

Possible Fix

I personally have no idea about this

Steps to Reproduce

  1. Create a new project using Spring Initializr
  2. Include problem-spring-web as dependency:
<dependency>
	<groupId>org.zalando</groupId>
	<artifactId>problem-spring-web-starter</artifactId>
	<version>0.27.0</version>
</dependency>
  1. Prepare a ValueObject
@Data(staticConstructor = "of")
@AllArgsConstructor
public class EchoMessage {
    String message;
}
  1. Prepare a RestController
@RestController
public class EchoController {
    @GetMapping(value = {"/echo", "/authorized/echo"})
    public EchoMessage echo(@RequestParam @NotEmpty String message) {
        return EchoMessage.of(message);
    }
}
  1. Configure Spring security
@Configuration
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // @formatter:off
        return http
                .authorizeRequests()
                    .antMatchers("/authorized/**")
                        .authenticated()
                    .anyRequest()
                        .anonymous()
                .and()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf()
                    .disable()
                .httpBasic()
                    .disable()
                .build();
        // @formatter:on
    }
}
  1. Write tests
@WebMvcTest
@ContextConfiguration(classes = SecurityTestConfiguration.class)
public class EchoControllerTest {
    private final MockMvc mockMvc;

    @Autowired
    public EchoControllerTest(MockMvc mockMvc) {
        this.mockMvc = mockMvc;
    }

    @Test
    public void testEcho() throws Exception {
        this.mockMvc.perform(get("/echo?message={message}", "Hello"))
                .andExpect(status().is(200))
                .andExpect(jsonPath("$.message", is("Hello")));
    }

    @Test
    public void testEchoWithoutMessage() throws Exception {
        this.mockMvc.perform(get("/echo"))
                .andExpect(header().string("Content-Type", "application/problem+json"))
                .andExpect(status().is(400))
                .andExpect(jsonPath("$.title", is("Bad Request")))
                .andExpect(jsonPath("$.status", is(400)));
    }

    @Test
    public void testEchoPost() throws Exception {
        this.mockMvc.perform(post("/echo?message={message}", "Hello"))
                .andExpect(header().string("Content-Type", "application/problem+json"))
                .andExpect(status().is(405))
                .andExpect(jsonPath("$.title", is("Method Not Allowed")))
                .andExpect(jsonPath("$.status", is(405)));
    }

    @Test
    public void testAuthorizedEcho() throws Exception {
        this.mockMvc.perform(get("/authorized/echo?message={message}", "Hello"))
                .andExpect(header().string("Content-Type", "application/problem+json"))
                .andExpect(status().is(401))
                .andExpect(jsonPath("$.title", is("Unauthorized")))
                .andExpect(jsonPath("$.status", is(401)));
    }
}

All tests fails except for testEcho.

I also tried simulating requests to /echo using curl, and got the same result.

Context

I find that if I register my own AdivceTrait bean in SecurityConfiguration, problem-spring-web will work again:

@Configuration
public class SecurityConfiguration {
      @ControllerAdvice
      public static class SecurityExceptionHandling implements ProblemHandling, SecurityAdviceTrait {}

      @Bean
      public AdviceTrait securityExceptionHandling() {
          return new SecurityExceptionHandling();
      }

    // Other beans
}

Your Environment

  • Spring Boot version: 2.5.6
  • problem-spring-web version: 0.27.0
  • Java version: Adopt OpenJDK 11

AND here is the runnable demo project attached: demo.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions