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

.NET 8集成云Keycloak登录回调报IDX10000令牌空错误求助

.NET 8对接云IAM Keycloak时出现IDX10000错误

我的应用基于.NET 8,两年前曾成功对接Docker部署的Keycloak,现在尝试对接云IAM中的Keycloak。已创建领域(Realm)、客户端(Client)并配置OpenID Connect,重新在应用中添加了OpenID集成(此前移除过Keycloak)。当访问带有[Authorize]特性的控制器时,会调用Keycloak登录页面,登录完成后返回回调页面,但.NET在AuthenticationHandlerProvider->GetHandlerAsync中抛出错误:

IDX10000: The parameter 'token' cannot be a 'null' or an empty object. (Parameter 'token')

可能的问题及解决步骤:

  1. 重复设置TokenValidationParameters
    代码中两次实例化TokenValidationParameters,第二次覆盖了第一次的配置,可能引发逻辑冲突。建议合并为一次设置:

    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        NameClaimType = "name",
        RoleClaimType = ClaimTypes.Role
    };
    
  2. ClientId配置不匹配
    代码中读取的是Keycloak:ClientIdPortal,但appsettings里的配置项是Keycloak:ClientId,这会导致ClientId为空,Keycloak无法正常返回token。需要统一配置项:

    • 要么将代码中的Keycloak:ClientIdPortal改为Keycloak:ClientId
    • 要么在appsettings中新增ClientIdPortal配置项
  3. CallbackPath与登录路径冲突
    当前options.CallbackPath = "/Authenticate/Index"与Cookie的LoginPath重复,回调时可能触发重复登录逻辑,导致token处理异常。建议修改为专用回调地址,比如:

    options.CallbackPath = new PathString("/signin-keycloak");
    

    同时要在Keycloak客户端配置的「Valid Redirect URIs」中添加这个新地址。

  4. 手动覆盖IssuerAddress的问题
    在OnRedirectToIdentityProvider事件中手动设置IssuerAddress会干扰中间件自动从Metadata获取端点的逻辑,建议注释掉这段代码:

    //OnRedirectToIdentityProvider = async context =>
    //{
    //    context.ProtocolMessage.IssuerAddress = builder.Configuration.GetValue<string>("Keycloak:Issuer");
    //    await Task.FromResult(0);
    //},
    
  5. 检查Keycloak客户端配置

    • 确保客户端「Access Type」设置为confidential(因为使用了ClientSecret)
    • 确认「Valid Redirect URIs」包含应用的回调地址
    • 检查「Standard Flow Enabled」是否开启(对应代码中的ResponseType = Code)

以下是相关配置:

Program.cs配置

builder.Services.AddAuthentication(options =>
{
    //Sets cookie authentication scheme
    options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
}).AddCookie(cookie =>
{
    //Sets the cookie name and maxage, so the cookie is invalidated.
    cookie.Cookie.Name = "keycloak.cookie";
    cookie.Cookie.MaxAge = TimeSpan.FromMinutes(60);
    cookie.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
    cookie.SlidingExpiration = true;
    cookie.LoginPath = "/Authenticate/Index";
    cookie.AccessDeniedPath = "/Authenticate/AccessDenied";
    cookie.ExpireTimeSpan = TimeSpan.FromSeconds(1800);
}).AddOpenIdConnect(options =>
{
    //options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.ClaimsIssuer = builder.Configuration.GetValue<string>("Keycloak:ServerRealm");
    //Keycloak server
    options.Authority = builder.Configuration.GetValue<string>("Keycloak:ServerRealm");
    //Keycloak client ID
    options.ClientId = builder.Configuration.GetValue<string>("Keycloak:ClientIdPortal");
    //Keycloak client secret
    options.ClientSecret = builder.Configuration.GetValue<string>("Keycloak:ClientSecret");
    //Keycloak .wellknown config origin to fetch config
    options.MetadataAddress = builder.Configuration.GetValue<string>("Keycloak:Metadata");
    options.GetClaimsFromUserInfoEndpoint = true;
    //Require keycloak to use SSL
    options.RequireHttpsMetadata = false;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.Scope.Add("openid");
    options.Scope.Add("profile");
    options.Scope.Add("email");
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true
    };
    //Save the token
    options.SaveTokens = true;
    //Token response type, will sometimes need to be changed to IdToken, depending on config.
    options.ResponseType = OpenIdConnectResponseType.Code;
    //SameSite is needed for Chrome/Firefox, as they will give http error 500 back, if not set to unspecified.
    options.NonceCookie.SameSite = SameSiteMode.Unspecified;
    options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
    options.TokenValidationParameters = new TokenValidationParameters
    {
        NameClaimType = "name",
        RoleClaimType = ClaimTypes.Role,
        ValidateIssuer = true
    };
    options.CallbackPath = new PathString("/Authenticate/Index");
    options.Events = new OpenIdConnectEvents
    {
        OnRedirectToIdentityProvider = async context =>
        {
            context.ProtocolMessage.IssuerAddress = builder.Configuration.GetValue<string>("Keycloak:Issuer");
            await Task.FromResult(0);
        },
        OnMessageReceived = context =>
        {
            Console.WriteLine("Message received");
            return Task.CompletedTask;
        },
        OnAuthenticationFailed = context =>
        {
            Console.WriteLine($"Authentication failed: {context.Exception.Message}");
            return Task.CompletedTask;
        }
    };
});

appsettings.json中的Keycloak配置

{
  "Keycloak": {
    "AuthenticationURL": "https://my-server-url/auth/realms/{realm}/protocol/openid-connect/token",
    "ClientId": "my-client",
    "ClientName": "my-client",
    "ClientSecret": "my-client-secret",
    "Issuer": "https://my-server-url/auth/realms/majorel/protocol/openid-connect/auth",
    "KeycloakURL": "https://my-server-url/auth",
    "LogoutURL": "https://my-server-url/auth/realms/{realm}/protocol/openid-connect/logout",
    "Metadata": "https://my-server-url/auth/realms/majorel/.well-known/openid-configuration",
    "Realm": "majorel",
    "ServerRealm": "https://my-server-url/auth/realms/majorel",
    "TokenURL": "https://my-server-url/auth/realms/{realm}/protocol/openid-connect/token"
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 09:37:02