Spring security 用MockMVC集成测试Spring引导

Spring security 用MockMVC集成测试Spring引导,spring-security,spring-boot,spring-test,spring-test-mvc,mockmvc,Spring Security,Spring Boot,Spring Test,Spring Test Mvc,Mockmvc,我在用MockMvc测试Spring启动应用程序时遇到了一些问题 我有以下测试课程: @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = {SpringConfiguration.class, SecurityConfiguration.class}) @IntegrationTest({"server.port=8080"}) @WebAppConfiguration public

我在用MockMvc测试Spring启动应用程序时遇到了一些问题

我有以下测试课程:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {SpringConfiguration.class, SecurityConfiguration.class})
@IntegrationTest({"server.port=8080"})
@WebAppConfiguration
public class DemoTest {

@Autowired
private EmbeddedWebApplicationContext webApplicationContext;

private MockMvc mockMvc;

@Before
public void setUp() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}

@Test
public void testGetAccountUnauthenticated() throws Exception {
    mockMvc.perform(get("/accounts/1").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isUnauthorized());
}
}
这将导致HTTP 200而不是401。我已启用组件扫描和自动配置,并且在我的SecuityConfiguration类中配置了spring security,如下所示:

@Configuration
@EnableWebSecurity
@EnableWebMvcSecurity // required for use of @AuthenticationPrincipal in MVC controllers.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
public void configure(WebSecurity web) {
    web.debug(true);
}

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    //set up authentication.
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().authenticated();
   // set up form login
}
}
@Resource(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
private Filter securityFilter;
MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(securityFilter)
如果我使用RestTemplate访问
http://localhost:8080/accounts/1
然后我得到了预期的行为(HTTP 401)

我看到了其他示例(例如),它们建议我自动连接
FilterChainProxy
,并使用
WebApplicationContext.addFilters(FilterChainProxy)
方法手动添加过滤器。但是,这对我来说实际上是失败的(
org.springframework.beans.factory.NoSuchBeanDefinitionException:找不到[org.springframework.security.web.FilterChainProxy]类型的合格bean

我有两个问题:

  • 为什么注入的WebApplicationContext不会自动使用SpringSecurity过滤器?即使我可以获取FilterChainProxy并手动添加它,JavaDoc for EmbeddedWebApplicationContext也会声明
  • 上下文中定义的任何{@linkservlet}或{@linkfilter}bean都将自动注册到嵌入式Servlet容器中

    因此,我不希望必须手动添加安全过滤器链,因为我(错误地?)认为,由于Spring Boot中的自动配置魔法,这会“正常工作”

  • 为什么应用程序上下文中没有FilterChainProxy?同样,也许我对自动配置的期望是不正确的——但我认为这将作为上下文配置的一部分进行配置
  • 提前谢谢你的建议


    编辑
    • 无法注入FilterChainProxy的原因是我将配置设置为

      公共void配置(WebSecurity web){ debug(true); }

    这实际上配置了一个
    org.springframework.security.web.debug.DebugFilter
    。现在,无论此调试设置如何,我都能够获得过滤器,其方法如下:

    @Configuration
    @EnableWebSecurity
    @EnableWebMvcSecurity // required for use of @AuthenticationPrincipal in MVC controllers.
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(WebSecurity web) {
        web.debug(true);
    }
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        //set up authentication.
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
       // set up form login
    }
    }
    
    @Resource(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    private Filter securityFilter;
    
    MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(securityFilter)
    
    如果我将其添加到MockMvcBuilder,如下所示:

    @Configuration
    @EnableWebSecurity
    @EnableWebMvcSecurity // required for use of @AuthenticationPrincipal in MVC controllers.
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(WebSecurity web) {
        web.debug(true);
    }
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        //set up authentication.
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
       // set up form login
    }
    }
    
    @Resource(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    private Filter securityFilter;
    
    MockMvcBuilders.webAppContextSetup(webApplicationContext).addFilters(securityFilter)
    
    然后它就如预期的那样工作了


    但是,我不明白为什么MockMVC会忽略过滤器,因为这对于测试请求似乎很重要,因为过滤器中可能发生任何可能影响测试结果的事情。此外,这意味着为了正确地进行测试,我需要查找servlet上下文中的所有过滤器,建立它们的优先级/url映射,并适当地添加它们。这似乎很容易出错,而且没有必要。

    我同意MockMVC可能更适合于测试SpringMVC和控制器中的自定义代码,正如@dave syer所评论的那样。因此,如果您希望同时使用自定义控制器代码(映射到URL的控制器的正确性;输入和输出对象的映射和验证;标准控制器;您的控制器)测试SpringMVC基础设施,而不利用堆栈的Servlet容器部分,MockMVC就可以了

    但是MockMVC也有添加过滤器的方法,因此它的设计允许在所描述的测试类型中使用过滤器。有时过滤器可能在控制器内部的代码中扮演功能性角色,否则就无法用MockMVC进行测试

    考虑到所有这些理论,我试图为我的测试模拟引导行为,其中过滤器将以Spring引导方式设置,并由我的测试拾取以与MockVMC一起使用。下面是我最后使用的一个片段。它当然可以得到增强,以更精确地模拟引导行为,并提取到一些定制的MockMVCBuilder中

    @Autowired
    private WebApplicationContext wac;
    
    private MockMvc mockMvc;
    
    @Before
    public void setUp() {
        Collection<Filter> filterCollection = wac.getBeansOfType(Filter.class).values();
        Filter[] filters = filterCollection.toArray(new Filter[filterCollection.size()]);
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilters(filters).build();
    }
    
    @Autowired
    私有WebApplicationContext wac;
    私有MockMvc-MockMvc;
    @以前
    公共作废设置(){
    Collection filterCollection=wac.getBeansOfType(Filter.class).values();
    Filter[]filters=filterCollection.toArray(新过滤器[filterCollection.size());
    mockMvc=MockMvcBuilders.webAppContextSetup(wac).addFilters(filters.build();
    }
    
    您试过这个吗

    import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
    
    ...
    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class AuthorizeTest {
    
        @Autowired
        private WebApplicationContext wac;
    
        @Before
        public void setup() {
            this.mockMvc = MockMvcBuilders
                    .webAppContextSetup(wac)
                    .apply(springSecurity())
                    .build();
        }
        ...
    
    }
    

    在我的例子中是403,不是401,但是你明白了

    好的,所以我还没有解决这个问题。然而,我相信MockMVC只是忽略EmbeddedWebApplicationContext上任何配置的过滤器。在我的调试器中,如果我计算表达式
    webApplicationContext.getServletContext().getFilterRegistrations()
    ,我可以看到一组过滤器,包括“springSecurityFilterChain”。虽然这并没有向我解释为什么不能注入FilterChainProxy,但它也提出了一个问题,即为什么MockMvc会忽略在Servlet上下文中配置的过滤器。正确。这就是为什么它被称为MockMVC(主要用于测试“DispatcherServlet”和SpringMVC)。但是在您的测试中,有一个完整的应用程序在8080端口上运行,所以如果您想进行端到端测试,为什么不使用
    restmplate
    呢?我选择使用MockMVC而不是restmplate的主要原因是,我觉得使用该API可以更好地生成更简洁的测试代码,并符合预期-例如“.andExpect(status().isUnauthorized())”。其次,我觉得作为不同的用户测试发出请求会稍微容易一些,而不必担心传递特殊的cookie值或请求头来确保我作为给定的用户进行了身份验证。不管怎样,我决定完全按照您的建议去做,使用RestTemplate,而不用担心MockMVC。