Mock SecurityContextHolder / Authentication always returning null

Viewed 12553

I'm aware this question gets asked a lot, but maybe I have some things that are particular to this. I'm trying to do some integration tests on a Spring Boot application that supports REST (not Spring MVC) and for some reason SecurityContextHolder.getContext().getAuthentication() always returns null, even when using @WithMockUser on the test. I'm not certain if this has to do with using profiles on the configuration classes, but so far we haven't had troubles with this.

Class

@Override
public ResponseEntity<EmployeeDTO> meGet() {
    Principal principal = SecurityContextHolder.getContext().getAuthentication();
    logger.debug("Endpoint called: me({})", principal);
    EmployeeDTO result;

    // Get user email from security context
    String email = principal.getName(); // NPE here

// ...
}

Test

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
        properties = {"eureka.client.enabled:false"})
@WithMockUser
@ActiveProfiles(value = "test")
public class MeControllerTest extends IntegrationSpringBootTest {

@Autowired
private TestRestTemplate restTemplate;

@MockBean
private SecurityContext securityContext;

@MockBean
private Authentication authentication;

@MockBean
private EmployeeRepository employeeRepository;

@BeforeClass
public static void setUp() {

}

@Before
@Override
public void resetMocks() {
    reset(employeeRepository);
}

@Test
public void meGet() throws Exception {
    when(securityContext.getAuthentication()).thenReturn(authentication);
    securityContext.setAuthentication(authentication);
    when(authentication.getPrincipal()).thenReturn(mockEmployee());
    SecurityContextHolder.setContext(securityContext);
    when(employeeRepository.findByEmail(anyString())).thenReturn(mockEmployee());

    ResponseEntity<EmployeeDTO> employeeDTOResponseEntity =
            this.restTemplate.getForEntity("/me", EmployeeDTO.class);
// ...
}

If I return a mock Principal instead of mockEmployee() the test cannot even start because this happens:

org.springframework.beans.factory.BeanCreationException: Could not inject field: private org.springframework.security.core.Authentication com.gft.employee.controller.MeControllerTest.authentication; nested exception is org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'org.springframework.security.core.Authentication#0' is expected to be of type 'org.springframework.security.core.Authentication' but was actually of type '$java.security.Principal$$EnhancerByMockitoWithCGLIB$$657040e6'

Additional clarifications: This Spring Boot app also uses OAuth2 for authorization, but it must be turned off for these tests. That's why we use profiles. Omitting the @ActiveProfiles annotation gives us a 401 Unauthorized error against the endpoint request.

I could use PowerMock but I would like to avoid it if possible.

3 Answers

Easier Way of writing Junit for Authentication SecurityContextHolder would be to mock them. Following is the working implementation of it. You can add the mock classes as per your need and then set context of SecurityContextHolder and then use when() to further mock and return proper mock value.

    AccessToken mockAccessToken = mock(AccessToken.class);
    Authentication authentication = mock(Authentication.class);
    SecurityContext securityContext = mock(SecurityContext.class);

    when(securityContext.getAuthentication()).thenReturn(authentication);

    SecurityContextHolder.setContext(securityContext);

    when(SecurityContextHolder.getContext().getAuthentication().getDetails()).thenReturn(mockSimpleUserObject);

This sample code is working for me. This code is using JUnit 5.

@SpringBootTest(classes = Application.class)
@AutoConfigureMockMvc //need this in Spring Boot test
public class LoginControllerIntegrationTest {

    // mockMvc is not @Autowired because I am customizing it @BeforeEach
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext context;

    @Mock
    DefaultOidcUser principal;

    @BeforeEach
    public void beforeEach() {
        Authentication authentication = mock(OAuth2AuthenticationToken.class);
        // Mockito.whens() for your authorization object
        SecurityContext securityContext = mock(SecurityContext.class);
        when(securityContext.getAuthentication()).thenReturn(authentication);
        when(authentication.getPrincipal()).thenReturn(principal);
        SecurityContextHolder.setContext(securityContext);
        // setting mockMvc with custom securityContext
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context).build();
    }

    @Test
    public void given_any_OAuth2AuthenticationToken_when_login_then_redirect_to_logout() throws Exception {

        final String loginName = "admin";
        // given
        // manipulate the principal as needed
        when(principal.getAttribute("unique_name")).thenReturn(loginName);

        // @formatter:off
        // when
        this.mockMvc.perform(get("/login"))
                .andDo(print())
        //then
                .andExpect(status().isFound())
                .andExpect(redirectedUrl("/logout"));
        // @formatter:off
    }
}
Related