控制器测试中@WithUserDetails能否正常注入@AuthenticationPrincipal?
问题产生的核心原因
- 测试环境未启用Spring Security过滤器链:MockMvc默认不会加载安全过滤器,
@WithUserDetails生成的认证上下文无法传递到控制器层,导致@AuthenticationPrincipal无法拿到对应对象 - UserDetailsService返回类型不匹配:
@WithUserDetails会调用测试环境中配置的UserDetailsService#loadUserByUsername方法获取用户对象,如果返回类型不是你自定义的AuthenticatedUser,会因为类型不匹配无法注入,最终显示为null - 测试类缺少安全测试注解:如果是仅加载Controller层的单元测试,需要额外添加
@AutoConfigureMockMvc、@AutoConfigureSecurity等注解启用安全相关自动配置
具体解决步骤
- 初始化MockMvc时启用安全过滤器配置
在测试类的初始化方法中添加springSecurity()配置,示例代码:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; @Autowired private WebApplicationContext webApplicationContext; private MockMvc mockMvc; @BeforeEach void initMockMvc() { mockMvc = MockMvcBuilders .webAppContextSetup(webApplicationContext) .apply(springSecurity()) // 关键配置,加载安全过滤器链 .build(); }
- 校验测试环境的UserDetailsService实现
确保测试环境注入的UserDetailsService的loadUserByUsername方法返回值就是AuthenticatedUser类型,不要返回默认的org.springframework.security.core.userdetails.User类。如果使用@WebMvcTest进行 slim 层测试,需要通过@MockBean模拟UserDetailsService,并指定mock返回的AuthenticatedUser对象。 - 备选方案:手动注入认证上下文
如果上述配置后仍有问题,可以跳过@WithUserDetails注解,直接在测试方法中手动设置安全上下文,示例代码:
@Test void shouldCreateVote_whenPost() throws Exception { // 构造测试用的AuthenticatedUser对象 AuthenticatedUser authenticatedUser = new AuthenticatedUser(BASIC_USER_ENTITY); // 生成认证对象 Authentication authentication = new UsernamePasswordAuthenticationToken( authenticatedUser, null, authenticatedUser.getAuthorities() ); // 写入安全上下文 SecurityContextHolder.getContext().setAuthentication(authentication); mockMvc.perform( post("/vote") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(VOTE_CREATION_ADRIANO)) ) .andExpect(status().isCreated()); Mockito.verify(voteService, times(1)) .addVote(refEq(BASIC_USER_ENTITY), refEq(VOTE_CREATION_ADRIANO)); }
内容的提问来源于stack exchange,提问作者user16722881
相关产品推荐
相关产品推荐

