Spring Boot集成OAuth2时无法Mock @PreAuthorize的测试问题
我正在为服务类编写单元测试,但不知道如何正确Mock控制器方法上的@PreAuthorize注解。具体场景及遇到的问题如下:
控制器方法
@GetMapping("/users") @PreAuthorize("hasAuthority('ADMIN')") public ResponseEntity<List<User>> getUsers() { return service.getUsers(); }
服务类方法
public ResponseEntity<List<User>> getUsers() { return new ResponseEntity<>(userRepository.findAll(), HttpStatus.OK); }
WebSecurity配置类
protected void configure(HttpSecurity http) throws Exception { http = http.cors().and().csrf().disable(); http = http .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and(); http = http .exceptionHandling() .authenticationEntryPoint( (request, response, ex) -> response.sendError( HttpServletResponse.SC_UNAUTHORIZED, ex.getMessage() ) ) .and(); http.authorizeRequests() .antMatchers(HttpMethod.GET, "/").permitAll() .anyRequest().authenticated(); http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); } public JwtAuthenticationConverter jwtAuthenticationConverter() { JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); converter.setJwtGrantedAuthoritiesConverter(jwt -> Optional.ofNullable(jwt.getClaimAsStringList("permissions")) .stream() .flatMap(Collection::stream) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()) ); return converter; }
编写的单元测试
@SpringBootTest @AutoConfigureMockMvc public class UserControllerTests { @Autowired private MockMvc mvc; @MockBean private UserService userService; @Test @WithMockJwtAuth(claims = @OpenIdClaims(otherClaims = @Claims(stringClaims = @StringClaim(name = "permissions", value = "{ADMIN}")))) public void getAllUsers_shouldBeSuccess() throws Exception { ArrayList<User> users = new ArrayList<>(); users.add(new User("0", true, new Role("USER"))); when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK)); mvc.perform(get("/users")) .andExpect(status().isOk()); } }
遇到的错误
调用mvc.perform时抛出以下错误:
java.lang.NoClassDefFoundError: org/springframework/security/web/context/SecurityContextHolderFilter
更新后的尝试
尝试使用@WithMockBearerTokenAuthentication注解,但仍出现相同错误;如果只保留@Test注解,接口返回401未授权:
@Test @WithMockBearerTokenAuthentication(attributes = @OpenIdClaims(otherClaims = @Claims(stringClaims = @StringClaim(name = "permissions", value = "{ADMIN}")))) public void getAllUsers_shouldBeSuccess() throws Exception { ArrayList<User> users = new ArrayList<>(); users.add(new User("0", true, new Role("USER"))); when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK)); mvc.perform(get("/users")) .andExpect(status().isOk()); }
解决方案
1. 修复依赖版本冲突
SecurityContextHolderFilter是Spring Security 5.6+引入的类,出现NoClassDefFoundError大概率是Spring Boot与Spring Security版本不兼容:
- 确保Spring Boot 2.6.x及以上版本搭配Spring Security 5.6.x及以上;
- 检查
pom.xml/build.gradle中的依赖,排除冲突的版本,优先使用Spring Boot父依赖管理的版本。
2. 手动设置SecurityContext(原生方案)
无需额外依赖,直接在测试中构造JWT认证对象并注入SecurityContext:
@Test public void getAllUsers_shouldBeSuccess() throws Exception { // 构造ADMIN权限 List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ADMIN")); // 构造模拟JWT Jwt jwt = Jwt.withTokenValue("mock-token") .header("alg", "none") .claim("permissions", Collections.singletonList("ADMIN")) .build(); // 生成认证对象 Authentication auth = new JwtAuthenticationToken(jwt, authorities); // 注入SecurityContext SecurityContextHolder.getContext().setAuthentication(auth); // 模拟服务返回 ArrayList<User> users = new ArrayList<>(); users.add(new User("0", true, new Role("USER"))); when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK)); // 执行请求验证 mvc.perform(get("/users")) .andExpect(status().isOk()); }
3. 修正spring-addons的使用
如果坚持使用第三方注解,注意两个关键点:
- 确保spring-addons版本与Spring Boot版本匹配;
- 权限值不需要包裹大括号,直接写
ADMIN即可:
@Test @WithMockBearerTokenAuthentication(attributes = @OpenIdClaims(otherClaims = @Claims(stringClaims = @StringClaim(name = "permissions", value = "ADMIN")))) public void getAllUsers_shouldBeSuccess() throws Exception { ArrayList<User> users = new ArrayList<>(); users.add(new User("0", true, new Role("USER"))); when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK)); mvc.perform(get("/users")).andExpect(status().isOk()); }
4. 简化测试上下文
使用@WebMvcTest替代@SpringBootTest,只加载控制器和必要的安全配置,减少依赖加载问题:
@WebMvcTest(UserController.class) @Import(WebSecurityConfig.class) public class UserControllerTests { @Autowired private MockMvc mvc; @MockBean private UserService userService; @Test public void getAllUsers_shouldBeSuccess() throws Exception { // 构造认证逻辑同上 List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ADMIN")); Jwt jwt = Jwt.withTokenValue("mock-token") .header("alg", "none") .claim("permissions", Collections.singletonList("ADMIN")) .build(); Authentication auth = new JwtAuthenticationToken(jwt, authorities); SecurityContextHolder.getContext().setAuthentication(auth); ArrayList<User> users = new ArrayList<>(); users.add(new User("0", true, new Role("USER"))); when(userService.getUsers()).thenReturn(new ResponseEntity<>(users, HttpStatus.OK)); mvc.perform(get("/users")).andExpect(status().isOk()); } }
内容的提问来源于stack exchange,提问作者Captain Jacky
相关产品推荐
相关产品推荐

