Spring Boot Cucumber测试中Reactive Security Context始终为空问题
Cucumber测试中ReactiveSecurityContextHolder获取不到上下文的问题排查
问题描述
我使用Spring Boot和Reactor开发服务,采用Cucumber进行测试时遇到以下问题:
在步骤定义的userIsAuthenticated方法中,调用ReactiveSecurityContextHolder.getContext()始终返回null,无法设置认证用户。但在常规Spring Boot测试中,通过@WithMockUser注解可正常获取安全上下文,且服务已正确注入到步骤定义类中。
核心原因
Cucumber的测试上下文初始化逻辑和Spring Boot常规测试存在差异:
@WithMockUser这类Spring Security测试注解无法直接在Cucumber步骤类中生效——因为Cucumber不会触发Spring测试框架的TestExecutionListener,而这类注解的生效依赖该监听器。- Reactive Security的上下文依赖Reactor线程的
Context绑定,Cucumber默认不会自动完成这个绑定操作。
缺失的配置与解决步骤
1. 引入必要的Cucumber依赖
首先确保POM中添加衔接Cucumber与Spring Security、Reactor的专用依赖,版本需与Cucumber核心版本保持一致:
<!-- Cucumber Spring Security 衔接依赖 --> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-spring-security</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency> <!-- Cucumber Reactor 支持依赖(步骤返回Mono/Flux时需要) --> <dependency> <groupId>io.cucumber</groupId> <artifactId>cucumber-reactor</artifactId> <version>${cucumber.version}</version> <scope>test</scope> </dependency>
2. 配置Cucumber测试上下文的Reactive Security支持
在你的Cucumber配置类(带有@CucumberContextConfiguration注解的类)上,添加Reactive Security的启用注解和测试配置导入:
import io.cucumber.spring.CucumberContextConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.test.context.support.ReactiveSpringSecurityTestConfiguration; @CucumberContextConfiguration @SpringBootTest @EnableWebFluxSecurity @Import(ReactiveSpringSecurityTestConfiguration.class) public class CucumberSpringConfig { }
3. 手动在步骤中绑定Reactive安全上下文
由于@WithMockUser无法直接生效,需要在userIsAuthenticated步骤方法里手动构建认证对象,并绑定到Reactor的Context中,同时确保步骤方法返回Mono<Void>以传递上下文:
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextImpl; import reactor.core.publisher.Mono; import reactor.util.context.Context; @Given("用户已认证") public Mono<Void> userIsAuthenticated() { // 构建模拟认证用户 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( "testUser", null, List.of(new SimpleGrantedAuthority("ROLE_USER")) ); SecurityContext securityContext = new SecurityContextImpl(authToken); // 将安全上下文绑定到Reactor Context,确保后续服务调用能获取到 return Mono.deferContextual(currentCtx -> { Context updatedCtx = currentCtx.put(ReactiveSecurityContextHolder.CONTEXT_KEY, securityContext); return Mono.empty().contextWrite(updatedCtx); }); }
4. 避免错误的同步获取方式
不要在非Reactive的步骤方法中直接调用ReactiveSecurityContextHolder.getContext().block(),这种同步阻塞操作会因为上下文还未绑定而返回null,必须通过Reactive流的上下文传递机制处理。
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

