Mock SecurityContextHolder / Authentication always returning null

10,824

Solution 1

I ended up using MockMvc despite the app not being Spring MVC-based. Additionally, I separated the SecurityContext calls into another service, but before doing that I could assert that the @WithMockUser annotation was working properly.

What's key for this to work is using these snippets at class level:

@WebMvcTest(MeController.class)
@Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
    // ...
}

Using @WebMvcTest facilitates not having to initialize a SecurityContext in the first place. You don't even have to call springSecurity(). You can just just the mockMvc.perform() operations as usual, and any calls to the SecurityContext will return whatever mocked user you specify, either with @WithMockUser or mocking the service that handles such a call.

Solution 2

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);
Share:
10,824
Manuel Santiago Yépez
Author by

Manuel Santiago Yépez

Usually Java developer, but I cheat on it with C# or RoR as my sidekicks.

Updated on June 21, 2022

Comments

  • Manuel Santiago Yépez
    Manuel Santiago Yépez about 2 years

    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.

  • afe
    afe over 3 years
    sorry this is not clear for me. when I need to mock two different users how am I supposed to pass the mocked access-tokens?