使用MockMvc进行混合调用的Spring MVC测试和使用@WithMockUser的服务失败 - MockMvc清除了SecurityContext

时间:2017-07-20 12:50:41

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

我在使用An Authentication object was not found in the SecurityContext注释的测试方法中通过MockMvc调用@Service后调用@RestController时出现@WithMockUser错误。

以相反的顺序调用可以正常工作,就像单独调用服务和控制器一样。 @Service正在清除SecurityContextHolder。我能想到的唯一解决方法是手动设置上下文。我错过了什么吗?

我需要调用服务来改变状态,然后测试控制器是否返回正确的状态。

@Service
public class SimpleService {

    @Secured("ROLE_SIMPLE_USER")
    public void doThis(Long id) {
        System.out.println("dothis : " + id);
    }

    @Secured("ROLE_SIMPLE_USER")
    public void doThat(Long id) {
        System.out.println("dothat : " + id);
    }
}

@RestController
@RequestMapping("/api")
@Secured("ROLE_SIMPLE_USER")
public class SimpleController {

    @Autowired
    private SimpleService simpleService;

    @PostMapping(value = "{id}/dothis")
    public ResponseEntity dothis(@PathVariable("id") Long id) {
        simpleService.doThis(id);
        return ResponseEntity.status(HttpStatus.OK).build();
    }

    @PostMapping(value = "{id}/dothat")
    public ResponseEntity dothat(@PathVariable("id") Long id) {
        simpleService.doThat(id);
        return ResponseEntity.status(HttpStatus.OK).build();
    }
}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles({"devh2"})
public class SimpleControllerTest {

    @Autowired
    private SimpleService simpleService;

    @Autowired
    private WebApplicationContext context;

    private MockMvc mockMvc;

    @Before
    public void setUpLiabilities() {
        this.mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity())
                .build();
    }

    @Test
    @WithMockUser(username="simple.user", password = "******", roles = {"SIMPLE_USER"})
    public void callViaControllers() throws Exception {
        mockMvc.perform(post("/api/1/dothis").with(csrf())).andExpect(status().isOk());
        mockMvc.perform(post("/api/1/dothat").with(csrf())).andExpect(status().isOk());
    }

    @Test
    @WithMockUser(username="simple.user", password = "******", roles = {"SIMPLE_USER"})
    public void callViaService() throws Exception {
        simpleService.doThis(1L);
        simpleService.doThat(1L);
    }

    @Test
    @WithMockUser(username="simple.user", password = "******", roles = {"SIMPLE_USER"})
    public void callViaServiceThenControllers() throws Exception {
        simpleService.doThis(1L);
        mockMvc.perform(post("/api/1/dothat").with(csrf())).andExpect(status().isOk());
    }

    @Test
    @WithMockUser(username="simple.user", password = "******", roles = {"SIMPLE_USER"})
    public void callViaControllerThenService() throws Exception {
        mockMvc.perform(post("/api/1/dothis").with(csrf())).andExpect(status().isOk());
        // ***FAILS*** because SecurityContextPersistenceFilter clears the SecurityContext ???
        simpleService.doThat(1L);
    }

    @Test
    @WithMockUser(username="simple.user", password = "******", roles = {"SIMPLE_USER"})
    public void callViaControllerThenServiceWithWorkaround() throws Exception {
        mockMvc.perform(post("/api/1/dothis").with(csrf())).andExpect(status().isOk());
        setSecurityContextAndThenCallDoThat(1L, "simple.user", "******", "ROLE_SIMPLE_USER");
    }

    // Workaround - set context manually
    private void setSecurityContextAndThenCallDoThat(long id, String username, String password, String... roles) {
        try {
            final SecurityContextImpl holder = new SecurityContextImpl();
            holder.setAuthentication(new TestingAuthenticationToken(username, password, roles));
            SecurityContextHolder.setContext(holder);

            this.simpleService.doThat(id);
        } finally {
            SecurityContextHolder.clearContext();
        }
    }
}

0 个答案:

没有答案