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

Joh*_*han 4 spring spring-mvc spring-security spring-data spring-boot

我已经以经典方式在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(....);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test1() {
                  mockMvc.perform(get(...)).andExpect(...);
    }
Run Code Online (Sandbox Code Playgroud)

或像这样的POST:

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

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test1() {
                  mockMvc.perform(post(...)).andExpect(...);
    }
Run Code Online (Sandbox Code Playgroud)

然后发生了意外情况:

  • 当测试方法未使用@WithMockUser注释时,由于401状态(未授权)是合理的,因此失败,因为没有完成基本身份验证
  • 当一个测试方法只是用一个空的@WithMockUser 注释而不指定任何凭据时,它开始通过,这是不合理的,因为我没有提供正确的身份验证数据(而是我将它们留为空)
  • 此时,当填写一些正确的凭据(例如@WithMockUser(用户名=“用户”,密码=“用户”,角色=“用户”)时,也通过了一种测试方法

问题:这是怎么回事?如何解决这种不良行为?

看起来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();
}
Run Code Online (Sandbox Code Playgroud)

cbl*_*to7 6

以下是如何使用 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(...);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

@Test
    public void shouldPost() {
                  mockMvc.perform(post(...)).with(csrf().asHeader())
                         .andExpect(...);
    }
Run Code Online (Sandbox Code Playgroud)


小智 5

此行为背后有两个原因:

  1. @WithMockUser注释不用于执行身份验证。它创建一个已经通过身份验证的用户。默认情况下,他的凭据是userpassword
  2. @WebMvcTest不执行MySecurityConfig.java。该批注创建具有安全默认值的 Spring嘲笑Mvc对象进行测试。这些安全性默认值由来应用。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();
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样重写测试。假设您使用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"));
  }
}
Run Code Online (Sandbox Code Playgroud)