测试时@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
相关产品推荐
相关产品推荐

