@WithMockUser没有选择Spring Security身份验证凭据

时间:2017-12-01 15:12:42

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

我已经使用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(用户名="用户",密码="用户",roles = " USER&#34)

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

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

编辑完整的配置方法如下

@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();
}

4 个答案:

答案 0 :(得分:5)

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

  1. @WithMockUser注释不用于执行身份验证。它创建了一个已经过身份验证的用户。默认情况下,他的凭据为userpassword
  2. @WebMvcTest未执行 MySecurityConfig.java。此注释会创建Spring mockMvc对象,其默认值为进行测试。这些安全默认值由org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityAutoConfiguration应用 您可以通过在MySecurityConfig方法上设置断点并在调试模式下重新运行测试来仔细检查这一点。断点不会被击中。
  3. 解决问题1

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

    解决问题2
    为所有Integration测试创建基类。它将配置应用了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"));
      }
    }
    

答案 1 :(得分:2)

以下是如何使用spring security的配置运行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请求等http请求提供跨站点请求伪造保护,因此需要使用crsf()

运行这些特定测试
@Test
    public void shouldPost() {
                  mockMvc.perform(post(...)).with(csrf().asHeader())
                         .andExpect(...);
    }

答案 2 :(得分:0)

有关信息,此代码使我能够使用从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());
}

答案 3 :(得分:0)

我遇到了同样的问题。我可以使用具有指定权限的@WithMockUser注释来解决此问题。

$sql = "SELECT user_id, name, count(user_id) as msgs_count from table group by (user_id, name)";

$result = $mysqli->query($query)

while ($user = mysqli_fetch_assoc($users)) {
    $user_id = $users['user_id'];
    $msgs_count= $users['msgs_count'];
    $count[$user_id] = $msgs_count;
    echo "{$user['name']} messages: $msgs_count";
}