模拟 SecurityContextHolder / Authentication 总是返回 null
Mock SecurityContextHolder / Authentication always returning null
我知道这个问题被问了很多,但也许我有一些特别的东西。我正在尝试对支持 REST(不是 Spring MVC)的 Spring 启动应用程序进行一些集成测试,并且出于某种原因 SecurityContextHolder.getContext().getAuthentication()
总是 returns null,即使在测试中使用 @WithMockUser
。我不确定这是否与在配置 类 上使用配置文件有关,但到目前为止我们还没有遇到这个问题。
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
// ...
}
测试
@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);
// ...
}
如果我 return 模拟 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$7040e6'
补充说明:此 Spring 启动应用程序也使用 OAuth2 进行授权,但必须为这些测试关闭它。这就是我们使用配置文件的原因。省略 @ActiveProfiles
注释会给我们一个针对端点请求的 401 Unauthorized 错误。
我可以使用PowerMock,但我想尽可能避免使用它。
尽管该应用不是 Spring 基于 MVC 的,但我最终还是使用了 MockMvc
。此外,我将 SecurityContext
调用分离到另一个服务中,但在此之前我可以断言 @WithMockUser
注释工作正常。
这个工作的关键是在 class 级别使用这些片段:
@WebMvcTest(MeController.class)
@Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
使用 @WebMvcTest
有助于不必首先初始化 SecurityContext
。您甚至不必调用 springSecurity()
。您可以像往常一样只执行 mockMvc.perform()
操作,对 SecurityContext
的任何调用都将 return 您指定的任何模拟用户,使用 @WithMockUser
或模拟处理此类的服务一个电话。
为身份验证 SecurityContextHolder 编写 Junit 的更简单方法是模拟它们。以下是它的工作实现。
您可以根据需要添加模拟 类,然后设置 SecurityContextHolder 的上下文,然后使用 when() 进一步模拟和 return 适当的模拟值。
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);
这个示例代码对我有用。此代码使用 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
}
}
我知道这个问题被问了很多,但也许我有一些特别的东西。我正在尝试对支持 REST(不是 Spring MVC)的 Spring 启动应用程序进行一些集成测试,并且出于某种原因 SecurityContextHolder.getContext().getAuthentication()
总是 returns null,即使在测试中使用 @WithMockUser
。我不确定这是否与在配置 类 上使用配置文件有关,但到目前为止我们还没有遇到这个问题。
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
// ...
}
测试
@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);
// ...
}
如果我 return 模拟 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$7040e6'
补充说明:此 Spring 启动应用程序也使用 OAuth2 进行授权,但必须为这些测试关闭它。这就是我们使用配置文件的原因。省略 @ActiveProfiles
注释会给我们一个针对端点请求的 401 Unauthorized 错误。
我可以使用PowerMock,但我想尽可能避免使用它。
尽管该应用不是 Spring 基于 MVC 的,但我最终还是使用了 MockMvc
。此外,我将 SecurityContext
调用分离到另一个服务中,但在此之前我可以断言 @WithMockUser
注释工作正常。
这个工作的关键是在 class 级别使用这些片段:
@WebMvcTest(MeController.class)
@Import({ControllerConfiguration.class, BeanConfiguration.class})
public class MeControllerTest {
// ...
}
使用 @WebMvcTest
有助于不必首先初始化 SecurityContext
。您甚至不必调用 springSecurity()
。您可以像往常一样只执行 mockMvc.perform()
操作,对 SecurityContext
的任何调用都将 return 您指定的任何模拟用户,使用 @WithMockUser
或模拟处理此类的服务一个电话。
为身份验证 SecurityContextHolder 编写 Junit 的更简单方法是模拟它们。以下是它的工作实现。 您可以根据需要添加模拟 类,然后设置 SecurityContextHolder 的上下文,然后使用 when() 进一步模拟和 return 适当的模拟值。
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);
这个示例代码对我有用。此代码使用 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
}
}