Spring Boot集成测试:如何预填充安全上下文的Principal
问题描述
我有一个暴露REST API的微服务,通过application.yml中的默认Spring Boot配置采用OAuth2进行安全保护:
security: oauth2: client: client-id: ... client-secret: ... grant-type: ... resource: user-info-uri: ... token-info-uri: ... prefer-token-info: false
API方法使用常规注解,用户在前端完成授权后,通过请求头携带Authorization: Bearer ...令牌向API发送授权请求。后端可在控制器方法中注入Principal参数读取用户详情,示例如下:
@CrossOrigin @RestController @RequestMapping("/foo") @ContextConfiguration public class MyController { @GetMapping("/all") public ResponseEntity<List<String>> sampleMethod(Principal user) { // <<<- 集成测试时Principal不应为null // 业务代码 } }
微服务实际运行时功能正常,但使用MockMvc执行@SpringBootTest集成测试时,Principal始终为null:
@ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc @DirtiesContext class MyControllerIT { @Autowired private MockMvc mockMvc; @Test void shouldReturnOk() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/all")) .andExpect(status().isOk()); } }
请问如何简便配置@SpringBootTest集成测试,使安全上下文预填充一个可由我控制的Principal?
解决方案
方法1:请求内直接绑定认证用户
这是最简便的方式,在测试请求中通过with(user(...))快速设置自定义Principal:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; @Test void shouldReturnOk() throws Exception { // 自定义Principal实现 Principal mockPrincipal = () -> "test-user-123"; mockMvc.perform(MockMvcRequestBuilders.get("/foo/all") .with(user(mockPrincipal.getName()))) .andExpect(status().isOk()); }
如果需要配置用户权限、角色,可构建完整的认证对象:
@Test void shouldReturnOkWithAuthorities() throws Exception { Authentication auth = User.withUsername("test-user") .password("无需实际密码") .authorities("ROLE_USER", "ROLE_ADMIN") .build(); mockMvc.perform(MockMvcRequestBuilders.get("/foo/all") .with(user(auth))) .andExpect(status().isOk()); }
方法2:全局配置测试认证用户
如果所有测试用例都需要相同的认证上下文,可在测试类上添加@WithMockUser注解,自动填充安全上下文:
@ExtendWith(SpringExtension.class) @SpringBootTest @AutoConfigureMockMvc @DirtiesContext @WithMockUser(username = "test-user", authorities = {"ROLE_USER"}) // 全局生效 class MyControllerIT { @Autowired private MockMvc mockMvc; @Test void shouldReturnOk() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/foo/all")) .andExpect(status().isOk()); } }
该注解会自动生成带有指定用户名、权限的认证对象,控制器中的Principal参数将被正确注入。
方法3:模拟真实OAuth2令牌请求
如果需要贴近生产场景的测试(携带Authorization头),可构造模拟令牌并添加到请求头:
@Test void shouldReturnOkWithOAuth2Token() throws Exception { // 模拟JWT令牌(实际测试可使用测试JWT生成工具生成符合格式的令牌) String mockToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QtdXNlciJ9.SW9D1XaZ7eS5PzQ0JfL8aA"; mockMvc.perform(MockMvcRequestBuilders.get("/foo/all") .header("Authorization", "Bearer " + mockToken)) .andExpect(status().isOk()); }
若需要服务端正确解析令牌,可通过@MockBean替换OAuth2资源服务器的令牌解析器,或在测试配置中设置模拟的令牌验证逻辑。
内容的提问来源于stack exchange,提问作者tiefenauer
相关产品推荐
相关产品推荐

