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

如何在.NET Core 9中实现Keycloak与xUnit集成测试?

解决Keycloak集成测试中Scheme重复的问题

问题根源

错误Scheme already exists: Bearer是因为测试代码中重复注册了认证方案:

  • 原项目的AddAuthenticationInternal已经注册了Bearer(JwtBearerDefaults.AuthenticationScheme)方案
  • 测试的ConfigureWebHost中又同时添加了AddJwtBearer和AddKeycloakWebApi(还指定了新的BearerTest scheme),导致冲突

修复步骤

1. 清理重复认证配置,重新指向Testcontainer Keycloak

修改测试Fixture的ConfigureWebHost方法,移除所有冗余的认证注册,只保留正确的Keycloak配置:

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
    builder.ConfigureServices(services =>
    {
        // 移除现有DbContext配置(保留原有代码)
        var descriptor = services.SingleOrDefault(
            d => d.ServiceType == typeof(DbContextOptions<AppDbContext>));
        if (descriptor != null)
        {
            services.Remove(descriptor);
        }

        // 彻底清理原有认证相关服务
        services.RemoveAll(typeof(AuthenticationOptions));
        services.RemoveAll(typeof(IAuthenticationSchemeProvider));
        services.RemoveAll(typeof(IAuthenticationHandler));

        // 使用测试容器的DbContext(保留原有代码)
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(_sqlServerContainer.GetConnectionString()));

        // 仅保留Keycloak认证配置,替换原有重复代码
        services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddKeycloakWebApi(options =>
            {
                options.Realm = "my-realm";
                options.AuthServerUrl = _keycloakContainer.GetBaseAddress();
                options.SslRequired = "none";
                options.Resource = "my-client";
                options.VerifyTokenAudience = true;
                options.Audience = "account";
                // 确保此secret与导入的my-realm.json中的客户端secret一致
                options.Credentials = new KeycloakClientInstallationCredentials
                {
                    Secret = "my-secret"
                };
            });
    });
}

2. 修正Keycloak Testcontainer启动配置

最新版Keycloak默认仅启用HTTPS,若你配置了ssl-required: none,需在容器启动时开启HTTP,并确保realm名称一致:

public async ValueTask InitializeAsync()
{
    // 启动SQL Server容器(保留原有代码)
    _sqlServerContainer = new MsSqlBuilder()
        .WithImage("mcr.microsoft.com/mssql/server:2022-latest")
        .Build();
    await _sqlServerContainer.StartAsync();

    // 修改Keycloak容器配置
    _keycloakContainer = new KeycloakBuilder()
        .WithImage("quay.io/keycloak/keycloak:latest")
        .WithName("keycloak_test")
        .WithResourceMapping("../../../../../keycloak-config/my-realm.json", "/opt/keycloak/data/import")
        // 使用start-dev命令,开启HTTP并导入realm
        .WithCommand("start-dev --import-realm --http-enabled true")
        // 映射HTTP端口8080(适配ssl-required: none配置)
        .WithPortBinding(8080, 8080)
        .WithEnvironment("KEYCLOAK_ADMIN", "admin")
        .WithEnvironment("KEYCLOAK_ADMIN_PASSWORD", "admin")
        .WithUsername("admin")
        .WithPassword("admin")
        .WithCleanUp(true)
        .Build();
    await _keycloakContainer.StartAsync();
}

3. 验证Realm配置一致性

确保:

  • 导入的my-realm.json中的realm名称为my-realm(与代码中options.Realm一致)
  • 客户端my-client的secret与代码中options.Credentials.Secret完全匹配
  • 测试容器的GetBaseAddress()返回的是http://localhost:8080(对应映射的端口)

额外提示

若需要在测试中获取有效的JWT Token,可以通过Keycloak的API调用获取:

// 从测试容器的Keycloak获取Token
var tokenClient = new HttpClient();
var tokenResponse = await tokenClient.PostAsync(
    $"{_keycloakContainer.GetBaseAddress()}/realms/my-realm/protocol/openid-connect/token",
    new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "password",
        ["client_id"] = "my-client",
        ["client_secret"] = "my-secret",
        ["username"] = "test-user", // 确保realm中存在此用户
        ["password"] = "test-password"
    }));
var tokenData = await tokenResponse.Content.ReadFromJsonAsync<JsonElement>();
var accessToken = tokenData.GetProperty("access_token").GetString();

在测试请求中添加Token:

client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

内容的提问来源于stack exchange,提问作者BrePi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 13:45:54