You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 13:57:51