3

I have a custom annotation with a custom filter to authorize incoming requests. The issue is that when I try to test my controllers with MockMvc the Spring Security Context is reset after each use of MockMvc therefore I can only run tests where I only call one endpoint.

Controller:

@RestController
class ElementController {

    @RequestMapping(value = ["getElement/{id}"], produces = [APPLICATION_XML_VALUE], method = [GET])
    @PreAuthorize("authentication.superUser")
    fun getElement(
        @PathVariable id: UUID
    ): String? {
        return // call service
    }

    // For brevity I've removed the other endpoint but it's declaration is similar.
}

Test Class:

@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles(INTEGRATION_TESTS)
@DirtiesContext
class ElementTest {
    @Autowired
    private lateinit var mockMvc: MockMvc

    @Test
    @WithMockCustomUser(superUser = true) // Custom Annotation created via Spring Docs
    fun `Given When Then`() {
    
        // Passes with correct authentication
        mockMvc.perform(
            put("putElement").content(/*data*/)
        ).andDo(print()).andExpect(status().isOk)

        // Fails because authentication context is empty
        val resultGet = mockMvc.perform(
            get("getElement/someId")
        ).andDo(print()).andExpect(status().isOk)
    }
}

The above call fails due to org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

I've tried looking through the code and it looks like MockMvc is resetting the security context after every call it makes. I've looked through the docs and it looks like I've set everything up correctly.

I've also looked at this GitHub issue where it's states:

@WithUserDetails will establish the same user for ever request. This is as designed.

Should this mean that my custom annotation should work the same way?

I'd really appreciate the help.

Update:

As a work-around I am setting the Sprint Security Context manually before the second call but I'd like to find a permanent solution.

val authentication = // set authentication
SecurityContextHolder.getContext().authentication = authentication
val resultGet = mockMvc.perform(
    get("getElement/someId")
).andDo(print()).andExpect(status().isOk)
Archmede
  • 1,592
  • 2
  • 20
  • 37
  • I guess you need to use session in the second call because security context is cleared after a request goes through the security filter chain. In your first call, get session like this: `MockHttpSession session = mockMvc.perform(....).andReturn().getRequest().getSession();` and use it in next call as `mockMvc.perform(...).session(session).andDo(...)` – Ritesh Sep 14 '20 at 19:08
  • @Ritesh thank you but that did not work for me – Archmede Sep 14 '20 at 19:24

0 Answers0