使用Spring对安全的REST控制器进行单元测试

5

我有一个使用Spring Boot编写的非常小的REST应用程序。

我想为身份验证编写一个单元测试,但即使我在测试中添加@MockWithUser,我仍会收到401错误。

重要的文件包括:

安全配置文件

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/**")
            .hasRole("USER")
            .anyRequest()
            .permitAll()
            .and()
            .anonymous().disable()
            .exceptionHandling()
            .authenticationEntryPoint(new org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint("headerValue"));
}

主应用程序和控制器
@Controller
@RequestMapping("/test")
@ComponentScan
@SpringBootApplication
public class MainApp {


@RequestMapping(method= RequestMethod.GET)
public @ResponseBody String sample(){

    return "Test";
}

public static void main(String[] args) throws Exception {
    SpringApplication.run(MainApp.class, args);
}
}

最后是测试(但它并未正常运行)

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = MainApp.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AuthenticationTest {

@Autowired
private TestRestTemplate testRestTemplate;

@Autowired
private WebApplicationContext context;

@Autowired
private Filter springSecurityFilterChain;

private MockMvc mvc;

@LocalServerPort
private int port;

@Before
public void setup() {
    mvc = MockMvcBuilders
            .webAppContextSetup(context)
            .addFilters(springSecurityFilterChain)
            .build();
}

@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void shouldReturn200WhenSendingRequestToControllerWithRoleUser() throws Exception {
    then(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
    mvc.perform(get("/test")).andExpect(status().isOk());

}

@Test
@WithMockUser(username="admin",roles={"USER","ADMIN"})
public void shouldAuthenticatedBeTrueWithRoleUser() throws Exception {
    then(SecurityContextHolder.getContext().getAuthentication().isAuthenticated());
}
}

如您所见,有两个测试案例。第二个通过了,但第一个没有(收到401而不是200作为响应代码,堆栈跟踪如下)。
您能告诉我如何正确地测试身份验证吗?
java.lang.AssertionError: 期望状态为<200>, 实际状态为<401> 在 org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:54) org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:81) 方法中抛出 在 org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.java:664) 在 org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171) 中使用 在 graphEndpoint.dataConnection.controller.AuthenticationTest.shouldReturn200WhenSendingRequestToControllerWithRoleUser(AuthenticationTest.java:64) 中使用 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) 在 org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) 在 org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) 在 org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) 在 org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) 在 org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 在 org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 在 org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 在 org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 在 org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) 在 org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:363) 在 org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) 在 org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114) 在 org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57) 在 org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66) 在 org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) 在 org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) 在 org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) 在 com.sun.proxy.$Proxy3.processTestClass(Unknown Source) 在 org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:109) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) 在 org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:377) 在 org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:54) 在 org.gradle.internal.concurrent.StoppableExecutorImpl$1.run(StoppableExecutorImpl.java:40) 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142
1个回答

6

仅使用addFilters不足以构建安全上下文。以下是一些解决方案:

在您的设置方法中替换

.addFilters(springSecurityFilterChain)

使用

.apply(springSecurity())

来自

org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;

仅此一个步骤就可以解决测试失败问题。但这样留下了一堆看起来很糟糕的类,可以通过使用WebMvcTest来大幅简化。以下是使用WebMvcTest更为简洁的测试版本。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = MainApp.class)
public class AuthenticationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(username = "admin", roles = {"USER", "ADMIN"})
    public void shouldReturn200WhenSendingRequestToControllerWithRoleUser() throws Exception {
        mockMvc.perform(get("/test")).andExpect(status().isOk());
    }
}

是的,看起来好多了。我对Spring还很陌生(基本上在过去几年中除了Selenium测试之外没有使用Java),所以我还不知道所有那些聪明的注释。我稍后会尝试这段代码(这台电脑上没有IDE)。非常感谢! - AsconX
2
我现在遇到了以下异常:"java.lang.IllegalStateException: Failed to load ApplicationContext"。一些研究告诉我要为上下文创建一个xml文件。但是应用程序本身在没有这样做的情况下运行。我真的需要将该文件添加到项目中进行测试吗,还是有其他智能注释可以使用? :) - AsconX
到目前为止我尝试的(没有改变任何东西):添加 @ContextConfiguration(classes = MainApp.class) 和添加 @SpringBootTest(...) 都不起作用,因为这样就会有多个上下文定义。 - AsconX
我发现这个错误是由完全不同的控制器引起的:Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'graphEndpoint.dataConnection.repository.CustomerRepository' - 但是这个存储库并没有在MainApp中使用,而MainApp是我现在想要测试的唯一控制器。有什么办法可以让测试不关注它? - AsconX
我不知道为什么,但是从 MainApp 中删除 @ComponentScan 解决了这个问题。非常感谢! - AsconX

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接