【问题标题】:@WithMockUser doesn't pick Spring Security auth credentials@WithMockUser 不选择 Spring Security 身份验证凭据
【发布时间】:2018-05-15 17:30:51
【问题描述】:

我已经在我的控制器中使用 Spring Security 以经典方式设置了基本身份验证,如下所示:

@EnableWebSecurity
@EnableGlobalMethodSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("user").password("user").roles("USER")
                .and()
                .withUser("admin").password("admin").roles("USER", "ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers(....);
    }
}

说到测试点,我使用 @WithMockUser 来注释我的测试。 GET 的测试可能如下所示:

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = SomeController.class)
public class SomeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test1() {
                  mockMvc.perform(get(...)).andExpect(...);
    }

或者像这样的 POST:

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = SomeController.class)
public class SomeControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test1() {
                  mockMvc.perform(post(...)).andExpect(...);
    }

然后意想不到的事情发生了:

  • 当一个测试方法没有用@WithMockUser注释时,它会因为401状态(未授权)而失败,这是合理的,因为没有完成基本身份验证
  • 当一个测试方法只是用一个空的 @WithMockUser 没有指定任何凭据进行注释时,它开始通过,这是不合理的,因为我没有提供正确的用于身份验证的数据(我将它们留空)
  • 此时,当填写一些正确的凭据时,测试方法也通过了,例如 @WithMockUser(username = "user", password = "user", roles = "USER")

问题:发生了什么事?如何解决这种不当行为?

看起来 Spring Security 已激活,但是我的测试没有使用我期望使用的身份验证。我必须自己模拟身份验证数据吗?

EDIT完整的配置方法如下

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers(URL1).hasAnyRole(ROLE_ADMIN)
            .antMatchers(URL2).hasAnyRole(ROLE_USER)
            .antMatchers(URL3).permitAll()
            .and()
            .httpBasic()
            .and()
            .csrf().disable();
}

【问题讨论】:

    标签: spring spring-mvc spring-boot spring-security spring-data


    【解决方案1】:

    这种行为背后有两个原因:

    1. @WithMockUser 注释不用于执行身份验证。它创建一个已经过身份验证的用户。默认情况下,他的凭据是 user : password
    2. @WebMvcTest 不执行 MySecurityConfig.java。 此注释创建 Spring mockMvc 对象具有安全默认值用于测试。这些安全默认值由org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration 应用 您可以通过在MySecurityConfig 方法上设置断点并在调试模式下重新运行测试来仔细检查这一点。没有达到断点。

    解决问题 1

    只需将您的方法更改为 @WithMockUser 注释的作用。它提供了已经登录的用户。仍然可以通过指定具体的用户名、密码和角色来测试 url 的安全性和角色配置。

    解决问题 2
    为所有集成测试创建一个基类。它将在应用了 Spring Security 的情况下配置 mockMvc。还要注意@SpringBootTest 注释。现在测试将使用MySecurityConfig.java

    import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
    import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
    
    import org.junit.Before;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.security.web.FilterChainProxy;
    import org.springframework.test.context.junit4.SpringRunner;
    import org.springframework.test.web.servlet.MockMvc;
    import org.springframework.web.context.WebApplicationContext;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public abstract class IT {
        @Autowired
        protected WebApplicationContext wac;
    
        @Autowired
        private FilterChainProxy springSecurityFilterChain;
    
        protected MockMvc mockMvc;
    
        @Before
        public void applySecurity() {
            this.mockMvc = webAppContextSetup(wac)
                .apply(springSecurity(springSecurityFilterChain))
                .build();
        }
    }
    

    像这样重写测试。假设您使用 http 基本身份验证。证书在测试中提供。注意:没有模拟用户注释。

    package com.example.demo;
    
    import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    
    import org.junit.Test;
    import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
    
    public class SomeControllerIT extends IT {
    
      @Test
      public void test1() throws Exception {
        mockMvc.perform(get("/some")
            .with(httpBasic("user", "user")))
            .andExpect(MockMvcResultMatchers.content().string("hello"));
      }
    }
    

    【讨论】:

    • 这就是答案
    【解决方案2】:

    以下是如何使用您的 spring 安全配置运行 mockMVC 测试:对于 USER 角色...

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = SomeController.class)
    public class SomeControllerTest {
    
        @Autowired
        private WebApplicationContext context;
    
        @Autowired
        private MockMvc mockMvc;
    
        @Before
        public void setup() {
          mockMvc = MockMvcBuilders
            .webAppContextSetup(context)
            .defaultRequest(get("/")
            .with(user("user").password("password").roles("USER")))
            .apply(springSecurity())
            .build();
        }
    
    
        @Test
        public void test1() {
                      mockMvc.perform(get(...)).andExpect(...);
        }
    }
    

    进行此更改后,您的 GET 测试现在应该可以工作了。

    由于 Spring Security 为 POST 和 DELETE 等 http 请求提供跨站点请求伪造保护,因此您需要使用 crsf() 运行这些特定测试

    @Test
        public void shouldPost() {
                      mockMvc.perform(post(...)).with(csrf().asHeader())
                             .andExpect(...);
        }
    

    【讨论】:

    • 不炒锅。如果我使用了错误的密码,它仍然可以顺利通过
    • 我可以看看你完整的 SecurityConfig 配置方法吗?我的想法是 .anyRequest().authenticated() 可能会丢失,这可以解释这种行为。
    • 是的,我已将其添加到问题的底部
    • .defaultRequest(get("/") 在你的代码中是什么意思?
    • 这意味着通过使用 MockMvcBuilders 的默认请求,确保每个请求都以特定用户身份运行。在此处查看更多信息docs.spring.io/spring-security/site/docs/4.0.x/reference/…
    【解决方案3】:

    我遇到了同样的问题。我可以通过指定权限的 @WithMockUser 注释来解决它。

    @Test
    @DisplayName("Should create and return the student")
    @WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
    public void should_create_student() throws Exception {
         mockMvc.perform(post(...)).andExpect(...);
    }
    

    【讨论】:

      【解决方案4】:

      有关信息,此代码允许我使用从 application.properties 文件中提取的凭据成功运行凭据测试,而无需使用 @WithMockUser 注释。

      @Autowired 
      MockMvc mvc;
      
      @Autowired
      private WebApplicationContext context;  
      
      @MockBean 
      BookRepository bookRepository;  
      
      @Test
      public void testFindAll() throws Exception {
          mvc = MockMvcBuilders.webAppContextSetup(context).build();           
          this.mvc.perform(get("/books").accept(MediaType.APPLICATION_JSON))
          .andExpect(status().isOk());
      }
      

      【讨论】:

        猜你喜欢
        • 2014-02-18
        • 2016-08-26
        • 2022-01-15
        • 2014-02-26
        • 2011-11-14
        • 2014-02-01
        • 2016-06-18
        • 1970-01-01
        • 2016-12-18
        相关资源
        最近更新 更多