Java集成测试:@WithMockUser测试Keycloak权限的GraphQL突变遇阻
解决方案
WebGraphQlTester 不支持 @WithMockUser 注解自动注入安全上下文,以下是几种可行的替代方案:
方案1:手动设置SecurityContext
直接在测试方法中手动构建Authentication对象并注入SecurityContextHolder,这是最直接可靠的方式:
@Autowired private WebGraphQlTester graphQlTester; @AfterEach void clearSecurityContext() { // 清理上下文,避免影响其他测试 SecurityContextHolder.clearContext(); } @Test void addEntity_with_readonly_role_return_403_forbidden() { // 构建带read_only角色的认证对象 Authentication auth = UsernamePasswordAuthenticationToken.authenticated( "Test", null, AuthorityUtils.createAuthorityList("ROLE_read_only") ); SecurityContextHolder.getContext().setAuthentication(auth); var entityInput = Map.of( "customId", "Testing", "name", "Test name", "description", "Test description", "entityType", "MANUAL", "notBefore", "2023-06-28T13:37:00Z", "expires", "2023-07-04T13:37:00Z" ); graphQlTester .documentName("entities") .operationName("AddEntity") .variable("input", entityInput) .execute() .errors() .satisfy(errors -> { assertThat(errors).hasSize(1); assertThat(errors.get(0).getErrorType()).isEqualTo(ErrorType.FORBIDDEN); }); }
注意:Spring Security 默认会给角色添加 ROLE_ 前缀,所以构建权限列表时需要显式添加,和 @WithMockUser 的 roles 属性行为保持一致。
方案2:模拟Keycloak JWT认证
如果需要更贴近真实的Keycloak认证场景(基于JWT),可以使用JwtAuthenticationToken来模拟:
@Test void addEntity_with_readonly_role_return_403_forbidden() { // 构建模拟JWT,贴合Keycloak返回的结构 Jwt jwt = Jwt.withTokenValue("mock-keycloak-token") .header("alg", "HS256") .claim("sub", "Test") .claim("realm_access", Map.of("roles", List.of("read_only"))) .build(); // 转换角色为Spring Security权限格式 List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_read_only"); Authentication auth = new JwtAuthenticationToken(jwt, authorities); SecurityContextHolder.getContext().setAuthentication(auth); // 后续测试逻辑 var entityInput = Map.of( "customId", "Testing", "name", "Test name", "description", "Test description", "entityType", "MANUAL", "notBefore", "2023-06-28T13:37:00Z", "expires", "2023-07-04T13:37:00Z" ); graphQlTester .documentName("entities") .operationName("AddEntity") .variable("input", entityInput) .execute() .errors() .satisfy(errors -> { assertThat(errors).hasSize(1); assertThat(errors.get(0).getErrorType()).isEqualTo(ErrorType.FORBIDDEN); }); }
这种方式更贴近真实环境中Keycloak的认证逻辑,测试结果参考性更强。
方案3:确保测试执行监听器生效
如果希望继续使用@WithMockUser,可以在测试类上显式指定测试执行监听器:
@SpringBootTest @TestExecutionListeners( listeners = WithSecurityContextTestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS ) class EntityGraphQlTest { @Autowired private WebGraphQlTester graphQlTester; @WithMockUser(username = "Test", roles={"read_only"}) @Test void addEntity_with_readonly_role_return_403_forbidden() { var entityInput = Map.of( "customId", "Testing", "name", "Test name", "description", "Test description", "entityType", "MANUAL", "notBefore", "2023-06-28T13:37:00Z", "expires", "2023-07-04T13:37:00Z" ); graphQlTester .documentName("entities") .operationName("AddEntity") .variable("input", entityInput) .execute() .errors() .satisfy(errors -> { assertThat(errors).hasSize(1); assertThat(errors.get(0).getErrorType()).isEqualTo(ErrorType.FORBIDDEN); }); } }
不过这种方式可能因WebGraphQlTester的上下文加载机制失效,推荐优先使用方案1或方案2。
内容的提问来源于stack exchange,提问作者Beast
相关产品推荐
相关产品推荐

