I have the version 5.6.10 in the following dependencies
spring-security-test
spring-security-core
spring-security-web
I have a controller with CSRF
@GetMapping(value = "/data")
public ResponseEntity<DataResponse> data(@RequestParam(required = false) Double param, CsrfToken token){
...
}
I have a JUnit test that was working before adding the , CsrfToken token
to Repository.
@WebMvcTest(controllers = Controller.class, excludeAutoConfiguration = {SecurityAutoConfiguration.class})
@ContextConfiguration(classes = {Controller.class, TestConfiguration.class})
class ControllerTest {
@Autowired private MockMvc mockMvc;
@Test
void test() throws Exception {
mockMvc.perform(get("/.../data?param=2.0")
.contextPath("/CONTEXT").servletPath("/.../data")
.contentType(MediaType.APPLICATION_JSON)
)
.andExpectAll(
status().isOk(),
...
)
.andReturn();
}
}
@WebAppConfiguration
@EnableWebMvc
public class TestConfiguration {
@Bean
ReactjsControllerExceptionHandler reactjsControllerExceptionHandler() {
return new ControllerExceptionHandler(); // is a @ControllerAdvice that extends ResponseEntityExceptionHandler. I think it does not matter for this case.
}
}
I am getting
No primary or single unique constructor found for interface org.springframework.security.web.csrf.CsrfToken
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: No primary or single unique constructor found for interface org.springframework.security.web.csrf.CsrfToken
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:502)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:72)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:596)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:201)
Caused by: java.lang.IllegalStateException: No primary or single unique constructor found for interface org.springframework.security.web.csrf.CsrfToken
at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:268)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.createAttribute(ModelAttributeMethodProcessor.java:219)
at org.springframework.web.servlet.mvc.method.annotation.ServletModelAttributeMethodProcessor.createAttribute(ServletModelAttributeMethodProcessor.java:85)
at org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:147)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:122)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:179)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:146)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1072)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:965)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
... 87 more
I already tried these options:
mockMvc.perform(get(...).with(csrf()))
.with(request -> {
request.setAttribute(CsrfToken.class.getName(), csrfToken);
return request;
})
CsrfToken csrfToken = Mockito.mock(CsrfToken.class);
Mockito.when(csrfToken.getToken()).thenReturn("myToken");
I am getting always the same error, what can I do?
Quite sure, that spring-security does not "csrf protect" GET endpoints "by default" (extend the later mentioned test with csrf "expectations" they won't hold/invalid csrf won't have effect)
CsrfConfigurer#requireCsrfProtectionMatcher
... The default is to ignore GET, HEAD, TRACE, OPTIONS ...
Nevertheless, some "magic" makes it possible to "populate" CsrfToken
param/attribute (also in spring-web(-security!) GET requests) ...
starter used: boot:2+web+security(+devtools)
(For "best version match" use:
<spring-security.version>5.6.10</spring-security.version>
(property))
Sa/imple controller (in child package of main class/root package):
@RestController
public class MyController {
@GetMapping(value = "/data")
public ResponseEntity<String> data(
@RequestParam(required = false) Double param
) {
return ResponseEntity.ok("Hello");
}
}
protected by spring-boot-starter-security
(defaults)
(a "comparable", realistic, security-integrating, suceeding) Test:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(controllers = MyController.class)
class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void testUnatuhorized() throws Exception {
mockMvc.perform(
get("/data?param=2.0")
.contentType(MediaType.APPLICATION_JSON))
.andExpect(
status().isUnauthorized()); // 1.
}
@Test
@WithMockUser // 2.
void testAuthorized1() throws Exception {
mockMvc.perform(
get("/data?param=2.0")
.contentType(MediaType.APPLICATION_JSON))
.andExpectAll(
status().isOk(),
content().string("Hello"));
}
@Test
void testAuthorized2() throws Exception {
mockMvc.perform(
get("/data?param=2.0")
.with(user("user")) // 3.
.contentType(MediaType.APPLICATION_JSON))
.andExpectAll(
status().isOk(),
content().string("Hello"));
}
}
Modifying controller to:
import org.springframework.security.web.csrf.CsrfToken;
//...
@GetMapping(value = "/data")
public ResponseEntity<String> data(
@RequestParam(required = false) Double param,
CsrfToken token /*!!*/) {
System.err.println(token); // !
return ResponseEntity.ok("Hello");
}
Doesn't break the test!
But excludeAutoConfiguration = {SecurityAutoConfiguration.class}
Does! (with exact same exception cause/message!;) :
org.springframework.web.util.NestedServletException:
Request processing failed; nested exception is java.lang.IllegalStateException:
No primary or single unique constructor found for interface org.springframework.security.web.csrf.CsrfToken ...
So the issue/solution must be in the missing SecurityAutoConfiguration
:your-version/ your custom TestConfiguration
.
... (digging source code) (CsrfToken, LazyCsrfTokenRepository, SecurityAutoConfiguration, EnableWebSecurity, ...
)
You miss a "csrf configuration" in your (test) context, which could be eligible to populate/resolve these arguments!!
excludeAutoConfiguration = {SecurityAutoConfiguration.class}
(on your @WebMvc-/SpringBoot-Test
)@EnableWebSecurity
annotation to your TestConfiguration
.Both configure a CsrfTokenRepository
in your (test) context, which is capable of populating/resolving these "request mapping arguments"...