首页 > 解决方案 > Spring Security @WithMockUser doesn't seem to work with state changing verbs (post, put..)

问题描述

Here is my setup:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .antMatchers("/signup", "/health").permitAll()
            .anyRequest().authenticated().and()
        .formLogin()
            .loginPage("/login")
            .permitAll()
...

Test class:

@ExtendWith(SpringExtension.class)
@WebMvcTest
@WithMockUser
class ApiControllerTest {
    ...

@WithMockUser works fine with below GET:

mockMvc.perform(get("/api/book/{id}", id))
        .andExpect(status().isOk())
...

but not with POST:

mockMvc.perform(post("/api/book")
        .contentType(MediaType.APPLICATION_JSON)
        .content(payload))
        .andExpect(status().isCreated())
...

When I look into the logs for MockHttpServletResponse, I notice the response is giving a redirect to login page, as follows:

MockHttpServletResponse:
           Status = 302
    Error message = null
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY", Location:"/login"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = /login
          Cookies = []

I know that @WithMockUser provides good amount of defaults for mocking user authentication. Why it is not working for stateful API request?

标签: spring-bootspring-mvcspring-security

解决方案


默认情况下,Spring Security 保护您免受跨站点请求伪造。

如果你不想要它,你必须在你的配置中主动禁用它。

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
        .authorizeRequests()
        ...

幸运的是你没有这样做,这样做是不安全的。

但是因此,每次执行 a 时都需要提供一个 csrf-token POST,在您的测试中也是如此!


import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;

...

mockMvc.perform(post("/api/book")
       .with(csrf()) // <--- missing in your test
       .contentType(MediaType.APPLICATION_JSON)
       .content(payload))
       .andExpect(status().isCreated());

现在你的测试应该可以工作了。


推荐阅读