Webflux中HttpSecurity用access配置时@WithMockUser失效,如何测试控制器?
我明白你遇到的痛点——@WithMockUser确实在这种自定义ReactiveAuthorizationManager的场景下不好使,因为它默认把Principal设成字符串,而你的代码里直接强转成CustomUserDetails,肯定拿不到正确的权限判断结果。下面给你一套可行的测试方案:
1. 先创建Fake CustomUserDetails实现
首先,你需要一个伪造的CustomUserDetails实例,用来模拟不同权限的用户。根据你的hasAuthorityForAnyBrand方法逻辑,我们可以写一个测试专用的实现:
public class FakeCustomUserDetails extends CustomUserDetails { private final boolean hasRequiredAuthority; // 构造方法根据你的CustomUserDetails实际字段调整,重点模拟权限判断逻辑 public FakeCustomUserDetails(String username, boolean hasRequiredAuthority) { super(username, "", Collections.emptyList()); // 填充父类需要的必填字段 this.hasRequiredAuthority = hasRequiredAuthority; } @Override public boolean hasAuthorityForAnyBrand(Module module, Action action) { // 测试时直接返回预设结果,不用走真实业务逻辑 return hasRequiredAuthority; } // 如果你的CustomUserDetails还有其他抽象方法,按需模拟实现即可 }
2. 在测试中手动构建Authentication并绑定到SecurityContext
因为Webflux是响应式框架,我们需要用ReactiveSecurityContextHolder把伪造的Authentication注入到请求上下文里。这里可以写一个工具方法复用逻辑:
private void setFakeUserInSecurityContext(boolean hasAuthority) { FakeCustomUserDetails fakeUser = new FakeCustomUserDetails("test-user", hasAuthority); Authentication authentication = new UsernamePasswordAuthenticationToken( fakeUser, null, // 测试场景下凭证可以设为null Collections.emptyList() // 权限列表可根据你的测试需求调整 ); authentication.setAuthenticated(true); // 标记为已认证状态 // 将Authentication放入SecurityContext,绑定到当前响应式上下文 ReactiveSecurityContextHolder.getContext() .contextWrite(securityContext -> securityContext.setAuthentication(authentication)) .block(); }
3. 编写WebTestClient测试用例
Webflux的控制器测试通常用WebTestClient,结合上面的工具方法,就能测试不同权限用户的访问情况:
@WebFluxTest(YourTargetApiController.class) @Import({HttpSecurityConfig.class, ModuleActionAuthorization.class}) // 导入你的安全配置和授权管理器 class YourTargetApiControllerTest { @Autowired private WebTestClient webTestClient; @Test void whenUserHasAuthority_thenCanAccessApi() { // 设置有权限的伪造用户 setFakeUserInSecurityContext(true); webTestClient.get() .uri("/api/your-test-endpoint") .exchange() .expectStatus().isOk(); } @Test void whenUserHasNoAuthority_thenIsForbidden() { // 设置无权限的伪造用户 setFakeUserInSecurityContext(false); webTestClient.get() .uri("/api/your-test-endpoint") .exchange() .expectStatus().isForbidden(); } }
进阶:自定义测试注解(可选)
如果多个测试类都需要用到这个逻辑,可以自定义一个注解+处理器,简化测试代码。比如:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WithFakeUser { boolean hasAuthority() default true; }
然后写一个TestExecutionListener或者结合@BeforeEach+反射来处理这个注解,自动帮你设置Fake用户,进一步减少重复代码。
补充说明下为什么@WithMockUser失效:它默认创建的UsernamePasswordAuthenticationToken里的Principal是字符串类型(比如你设置的username),而你的ModuleActionAuthorization里直接把a.getPrincipal()强转成CustomUserDetails,这会导致类型转换异常,或者根本触发不了正确的权限判断逻辑。所以必须手动构建带CustomUserDetails实例的Authentication。
内容的提问来源于stack exchange,提问作者user3139545

