You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot集成OAuth2时无法Mock @PreAuthorize的测试问题

测试带@PreAuthorize注解的Spring Boot控制器方法遇到的问题

我正在为服务类编写单元测试,但不知道如何正确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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 20:35:23