diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java index 2f5bb228fd7..2c3439994cb 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.java @@ -38,6 +38,8 @@ import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; +import org.springframework.web.accept.ContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; import static org.springframework.security.config.Customizer.withDefaults; @@ -65,6 +67,8 @@ class HttpSecurityConfiguration { private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); + private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy(); + @Autowired void setObjectPostProcessor(ObjectPostProcessor objectPostProcessor) { this.objectPostProcessor = objectPostProcessor; @@ -89,6 +93,11 @@ void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityCont this.securityContextHolderStrategy = securityContextHolderStrategy; } + @Autowired(required = false) + void setContentNegotiationStrategy(ContentNegotiationStrategy contentNegotiationStrategy) { + this.contentNegotiationStrategy = contentNegotiationStrategy; + } + @Bean(HTTPSECURITY_BEAN_NAME) @Scope("prototype") HttpSecurity httpSecurity() throws Exception { @@ -143,6 +152,7 @@ private void applyDefaultConfigurers(HttpSecurity http) throws Exception { private Map, Object> createSharedObjects() { Map, Object> sharedObjects = new HashMap<>(); sharedObjects.put(ApplicationContext.class, this.context); + sharedObjects.put(ContentNegotiationStrategy.class, this.contentNegotiationStrategy); return sharedObjects; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java index bb83076b1c8..1882b2c0e0d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configuration/HttpSecurityConfigurationTests.java @@ -60,12 +60,16 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.NativeWebRequest; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; @@ -311,6 +315,14 @@ public void configureWhenDefaultConfigurerAsSpringFactoryThenDefaultConfigurerAp assertThat(configurer.configure).isTrue(); } + @Test + public void getWhenCustomContentNegotiationStrategyThenUses() throws Exception { + this.spring.register(CustomContentNegotiationStrategyConfig.class).autowire(); + this.mockMvc.perform(get("/")); + verify(CustomContentNegotiationStrategyConfig.CNS, atLeastOnce()) + .resolveMediaTypes(any(NativeWebRequest.class)); + } + @RestController static class NameController { @@ -489,6 +501,30 @@ void user(HttpServletRequest request) { } + @Configuration + @EnableWebSecurity + static class CustomContentNegotiationStrategyConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeHttpRequests((requests) -> requests + .anyRequest().authenticated() + ); + // @formatter:on + return http.build(); + } + + static ContentNegotiationStrategy CNS = mock(ContentNegotiationStrategy.class); + + @Bean + static ContentNegotiationStrategy cns() { + return CNS; + } + + } + static class DefaultConfigurer extends AbstractHttpConfigurer { boolean init;