Mock SecurityContextHolder / Authentication始终返回null

时间:2017-06-30 15:18:33

标签: spring spring-security mockito junit4 spring-test

我知道这个问题被问了很多,但也许我有一些特别的事情。我试图在支持REST(而不是Spring MVC)的Spring Boot应用程序上进行一些集成测试,并且由于某种原因SecurityContextHolder.getContext().getAuthentication()总是返回null,即使在测试中使用@WithMockUser也是如此。我不确定这是否与在配置类上使用配置文件有关,但到目前为止我们还没有遇到麻烦。

@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

// ...
}

测试

@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);
// ...
}

如果我返回模拟Principal而不是mockEmployee(),则测试甚至无法启动,因为这会发生:

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'

其他说明:此Spring Boot应用程序也使用OAuth2进行授权,但必须关闭这些测试。这就是我们使用配置文件的原因。省略@ActiveProfiles注释会为端点请求提供401 Unauthorized错误。

可以使用PowerMock但我想尽可能避免使用它。

3 个答案:

答案 0 :(得分:1)

尽管应用程序不是基于Spring MVC的,我最终还是使用了MockMvc。另外,我将SecurityContext调用分成另一个服务,但在此之前我可以断言@WithMockUser注释正常工作。

这项工作的关键是在课堂级别使用这些片段:

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

使用@WebMvcTest有助于不必首先初始化SecurityContext。你甚至不必打电话给springSecurity()。您可以照常执行mockMvc.perform()操作,对SecurityContext的任何调用都将返回您指定的任何模拟用户,使用@WithMockUser或模拟处理此类调用的服务。< / p>

答案 1 :(得分:1)

更容易编写Junit for Authentication的方法SecurityContextHolder将模拟它们。以下是它的工作实施。 您可以根据需要添加模拟类,然后设置SecurityContextHolder的上下文,然后使用when()进一步模拟并返回正确的模拟值。

    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);

答案 2 :(得分:0)

此示例代码对我有用。此代码使用 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
    }
}