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

测试时@PreAuthorize中@SecurityService表达式求值失败问题

@WebMvcTest测试时@PreAuthorize无法识别自定义SecurityService的解决方案

我在应用端点的@PreAuthorize注解中使用自定义SecurityService的方法实现授权逻辑,生产环境运行正常,但使用@WebMvcTest编写控制器测试时,请求返回500错误,日志提示无法解析表达式@SecurityService.hasAccessToUser(#userId)。

相关代码

控制器代码

@RestController
@RequestMapping("test")
@RequiredArgsConstructor
public class TestController {
    private final TestService testService;

    @GetMapping("/{userId}")
    @ResponseStatus(HttpStatus.OK)
    @PreAuthorize("@SecurityService.hasAccessToUser(#userId)")
    List<TestDTO> getUserData(@PathVariable String userId) {
        return testService.fetchUserData(userId);
    }
}

SecurityService代码

@Service
@RequiredArgsConstructor
public class SecurityService {

    private Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    private String getUserId() {
        return (String) getAuthentication().getPrincipal();
    }

    public boolean hasAccessToUser(String userId) {
        return getUserId().equals(userId);
    }
}

测试代码

@WebMvcTest(TestController.class)
public class TestControllerTest {

    private static final String BASE_URL = "/test";

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private TestService testService;

    @MockBean
    private SecurityService securityService;

    @MockBean
    private TestRepository testRepository;


    @SneakyThrows
    @Test
    void shouldGetUserDataGivenUserId() {
        mockMvc.perform(asUser(get(BASE_URL + "/" + TEST_USER_ID)))
                .andExpect(status().is(Response.SC_OK));

        verify(testService).fetchUserData(TEST_USER_ID);
    }
}

错误日志片段

Internal Server Error: Failed to evaluate expression '@SecurityService.hasAccessToUser(#userId)'"}]
 at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
 ...

原因分析

@WebMvcTest默认仅加载Web层相关配置,不会自动启用Spring Security的方法级安全支持,同时Spring Security的表达式解析器无法在测试上下文里找到SecurityService的bean引用。

解决步骤

1. 启用方法级安全配置

在测试类上添加@EnableMethodSecurity(Spring Security 5.6+推荐)或旧版@EnableGlobalMethodSecurity(prePostEnabled = true),确保@PreAuthorize注解能被解析:

@WebMvcTest(TestController.class)
@EnableMethodSecurity // 启用方法级安全支持
public class TestControllerTest {
    // ... 原有代码
}

2. 预设SecurityService的mock逻辑

测试中用@MockBean模拟了SecurityService,需要为hasAccessToUser方法预设返回值,避免授权逻辑失败:

@SneakyThrows
@Test
void shouldGetUserDataGivenUserId() {
    // 预设授权方法返回true,通过权限校验
    when(securityService.hasAccessToUser(TEST_USER_ID)).thenReturn(true);
    
    mockMvc.perform(asUser(get(BASE_URL + "/" + TEST_USER_ID)))
            .andExpect(status().is(Response.SC_OK));

    verify(testService).fetchUserData(TEST_USER_ID);
}

3. 确保认证上下文正确设置

如果自定义的asUser方法未正确设置SecurityContext中的Authentication,可改用Spring Security提供的@WithMockUser注解快速设置测试用户:

@SneakyThrows
@Test
@WithMockUser(username = TEST_USER_ID) // 直接设置当前认证用户ID
void shouldGetUserDataGivenUserId() {
    when(securityService.hasAccessToUser(TEST_USER_ID)).thenReturn(true);
    
    mockMvc.perform(get(BASE_URL + "/" + TEST_USER_ID))
            .andExpect(status().is(Response.SC_OK));

    verify(testService).fetchUserData(TEST_USER_ID);
}

备选方案:使用@SpringBootTest替代@WebMvcTest

若希望加载完整应用上下文(包含Spring Security全量配置),可改用@SpringBootTest配合@AutoConfigureMockMvc,无需额外配置方法级安全:

@SpringBootTest
@AutoConfigureMockMvc
public class TestControllerTest {
    // ... 原有代码
}

内容的提问来源于stack exchange,提问作者Quilir

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 23:01:24