I get An Authentication object was not found in the SecurityContext
error when I call a @Service
after calling a @RestController
through MockMvc in a test method annotated with @WithMockUser
.
Calling in the opposite order works fine as does calling the service and controller individually. The @Service
is clearing the SecurityContextHolder
. The only workaround I can think of is to set the context manually. Am I missing something?
I need to call the service to mutate state and then test that the controller returns the correct state.
@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();
}
}
}