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

Spring Boot微服务JUnit测试:含@PreAuthorize的方法角色配置问题

问题:使用@WithMockUser测试带@PreAuthorize注解的控制器时出现403 Forbidden错误

背景

我在订单控制器的方法中添加了@PreAuthorize("hasAuthority('ROLE_ADMIN')")和@PreAuthorize("hasAuthority('ROLE_USER')")注解,认证服务已完成用户、管理员角色的登录方法定义,现在需要修改控制器测试逻辑。

给placeOrder方法添加@PreAuthorize("hasAuthority('ROLE_USER')")后,使用JWT令牌的测试方法可以正常通过,但尝试用@WithMockUser(roles="USER")和@WithMockUser(roles="ADMIN")模拟角色时,始终返回403 Forbidden错误。此前添加过WebSecurityConfig配置类但未解决问题,已暂时移除。


相关代码

订单控制器方法

@PreAuthorize("hasAuthority('ROLE_USER')")
@PostMapping("/placeorder")
public ResponseEntity<Long> placeOrder(@RequestBody OrderRequest orderRequest) {

    log.info("OrderController | placeOrder is called");
    log.info("OrderController | placeOrder | orderRequest: {}", orderRequest.toString());

    long orderId = orderService.placeOrder(orderRequest);
    log.info("Order Id: {}", orderId);
    return new ResponseEntity<>(orderId, HttpStatus.OK);
}

可正常运行的测试方法(使用JWT)

@Test
@DisplayName("Place Order -- Success Scenario")
void test_When_placeOrder_DoPayment_Success() throws Exception {

    OrderRequest orderRequest = getMockOrderRequest();
    String jwt = getJWTTokenForRoleUser();

    MvcResult mvcResult
            = mockMvc.perform(MockMvcRequestBuilders.post("/order/placeorder")
                    .contentType(MediaType.APPLICATION_JSON_VALUE)
                    .header("Authorization", "Bearer " + jwt)
                    .content(objectMapper.writeValueAsString(orderRequest)))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andReturn();

    String orderId = mvcResult.getResponse().getContentAsString();

    Optional<Order> order = orderRepository.findById(Long.valueOf(orderId));
    assertTrue(order.isPresent());

    Order o = order.get();
    assertEquals(Long.parseLong(orderId), o.getId());
    assertEquals("PLACED", o.getOrderStatus());
    assertEquals(orderRequest.getTotalAmount(), o.getAmount());
    assertEquals(orderRequest.getQuantity(), o.getQuantity());
}

项目依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

已移除的WebSecurityConfig配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests(
                        authorizeRequest -> authorizeRequest
                                .anyRequest()
                                .authenticated());

        return http.build();
    }
}

解决方案

核心原因

  1. 移除WebSecurityConfig后,@EnableGlobalMethodSecurity(prePostEnabled = true)未生效,导致@PreAuthorize注解不被Spring Security处理;
  2. 测试时未禁用CSRF防护,POST请求被Spring Security拦截;
  3. @WithMockUser的使用方式与权限校验逻辑不匹配(虽表面匹配,但需确保配置生效)。

具体修复步骤

1. 恢复并修正WebSecurityConfig配置类

必须启用方法级安全才能让@PreAuthorize生效,同时测试环境需禁用CSRF:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // Spring Boot 2.7+/3.x推荐用这个替代@EnableGlobalMethodSecurity
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityWebFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .anyRequest().authenticated()
                )
                .csrf(csrf -> csrf.disable()); // 测试环境禁用CSRF,生产环境按需配置

        return http.build();
    }
}

2. 正确使用@WithMockUser

两种写法均匹配hasAuthority('ROLE_USER')的校验逻辑:

// 写法1:通过roles参数自动生成ROLE_USER权限
@Test
@DisplayName("Place Order -- Success with Mock User")
@WithMockUser(roles = "USER")
void test_When_placeOrder_WithMockUser_Success() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders.post("/order/placeorder")
                    .contentType(MediaType.APPLICATION_JSON_VALUE)
                    .content(objectMapper.writeValueAsString(getMockOrderRequest())))
            .andExpect(MockMvcResultMatchers.status().isOk());
}

// 写法2:直接指定权限字符串,与控制器注解完全匹配
@Test
@DisplayName("Place Order -- Success with Mock Authority")
@WithMockUser(authorities = "ROLE_USER")
void test_When_placeOrder_WithMockAuthority_Success() throws Exception {
    // 测试逻辑同上
}

3. 确保测试类正确加载上下文

测试类需添加@SpringBootTest和@AutoConfigureMockMvc,保证Spring Security配置与MockMvc初始化:

@SpringBootTest
@AutoConfigureMockMvc
class OrderControllerTest {

    @Autowired
    private MockMvc mockMvc;

    // 其他依赖注入与测试方法...
}

内容的提问来源于stack exchange,提问作者Sercan Noyan Germiyanoğlu

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 17:55:30