Spring MVC WebClient客户端凭证流OAuth2集成测试编写方法
Spring MVC OAuth2客户端凭证流场景集成测试实现方案
测试核心覆盖三个验证目标:
- 客户端凭证流配置正确性:可按配置参数从授权服务器获取带指定scope的有效JWT
- 接口权限控制逻辑:无令牌、令牌缺少必要scope时返回对应鉴权失败状态码,携带合法scope令牌可通过鉴权
- 业务功能正确性:合法请求下接口返回符合预期的业务响应
前置依赖准备
测试采用「本地启动服务+WireMock模拟授权服务器」的方式,不依赖真实外部授权服务,保证测试稳定性,test作用域引入以下依赖:
spring-boot-starter-test:提供Spring Boot测试核心能力、随机端口启动、测试上下文加载支持spring-security-test:提供JWT模拟、安全上下文测试工具- WireMock:用于模拟授权服务器的令牌端点、JWK端点,支持请求校验、自定义响应
测试环境配置
在测试资源目录下创建application-test.yaml,将授权服务器地址替换为WireMock的本地监听地址,其余客户端配置(客户端ID、密钥、授权模式、scope)与生产配置保持一致,示例配置:
spring: security: oauth2: resourceserver: jwt: jwk-set-uri: http://localhost:${wiremock.server.port}/oauth2/jwks client: provider: mock-auth: token-uri: http://localhost:${wiremock.server.port}/oauth2/token registration: my-service: provider: mock-auth client-id: test-client client-secret: test-secret authorization-grant-type: client_credentials scope: message.send wiremock: server: port: 0 # 随机端口启动,避免端口冲突
分场景测试实现
测试类基础配置如下,采用随机端口启动完整Spring上下文,激活test配置文件,自动启动WireMock:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @AutoConfigureWireMock class MessageApiIntegrationTest { @Autowired private AuthClientManager authClientManager; @LocalServerPort private int servicePort; @Autowired private ObjectMapper objectMapper; private WebClient testWebClient; @BeforeEach void setUp() { // 初始化指向本地测试服务的基础WebClient testWebClient = WebClient.builder() .baseUrl("http://localhost:" + servicePort) .build(); // 重置WireMock桩,避免用例间互相影响 WireMock.reset(); } }
1. 客户端凭证流配置正确性验证
首先提前生成一对RSA密钥对,用于模拟JWT签发与校验:
- 用私钥签名生成测试JWT,根据不同用例设置不同scope、过期时间等字段
- 用公钥构造JWK响应,模拟授权服务器的JWK端点
- 配置WireMock令牌端点桩:校验请求必须携带
grant_type=client_credentials、scope=message.send参数,且请求头携带正确的客户端Basic认证(客户端ID:密钥的Base64编码),校验通过则返回签名后的有效JWT,否则返回401
测试逻辑:
- 调用
authClientManager获取my-service客户端对应的携带OAuth凭证的WebClient - 发起测试请求,校验WireMock收到的请求头中是否携带格式正确的
Authorization: Bearer <token> - 解析请求中的JWT,校验签发主体、scope、过期时间是否符合配置预期
2. 接口权限控制逻辑验证
这部分可以结合Spring Security Test提供的注解快速构造安全上下文,不需要每次走完整的令牌获取流程,提升测试执行效率:
- 无令牌请求用例:直接用基础testWebClient调用
/v1/message接口,断言返回401状态码 - 权限不足用例:用
@WithMockJwt(authorities = "SCOPE_other.scope")注解标记测试方法,构造带其他scope的JWT上下文,调用接口断言返回403状态码 - 权限通过用例:用
@WithMockJwt(authorities = "SCOPE_message.send")注解标记测试方法,构造带正确scope的JWT上下文,调用接口断言不会被权限拦截,可进入业务逻辑
示例代码:
@Test void whenRequestWithoutToken_thenReturn401() { testWebClient.post() .uri("/v1/message") .contentType(MediaType.APPLICATION_JSON) .bodyValue(new SendMessageRequest("test@example.com", "test content")) .exchange() .expectStatus().isUnauthorized(); } @Test @WithMockJwt(authorities = "SCOPE_wrong.scope") void whenRequestWithoutRequiredScope_thenReturn403() { testWebClient.post() .uri("/v1/message") .contentType(MediaType.APPLICATION_JSON) .bodyValue(new SendMessageRequest("test@example.com", "test content")) .exchange() .expectStatus().isForbidden(); }
3. 端到端业务功能验证
这个用例走完整真实流程:
- 配置WireMock的JWK端点返回公钥、令牌端点返回带
message.sendscope的有效JWT - 通过
authClientManager获取my-service对应的、自动携带OAuth凭证的WebClient - 构造合法的邮件发送请求参数,调用本地服务的
/v1/message接口 - 断言返回200状态码,且返回体业务字段符合预期,同时校验服务内部业务逻辑(比如邮件记录落库、下游调用触发等)是否正常执行
注意事项
- 不要在集成测试中连接真实授权服务器,否则测试稳定性会受外部服务可用性、网络环境影响,CI环境执行失败率极高
- Spring Security对OAuth2 scope的默认权限前缀是
SCOPE_,测试构造权限、写权限校验表达式时注意前缀匹配,hasScope('message.send')和hasAuthority('SCOPE_message.send')是等价写法 - 模拟JWT时注意
exp过期时间要设置为当前时间之后,否则会被令牌校验逻辑判定为无效令牌 - 每个用例执行后要重置WireMock桩,避免上一个用例的模拟配置影响后续用例的断言结果
内容的提问来源于stack exchange,提问作者skyho
相关产品推荐
相关产品推荐

