.NET 8集成云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')
可能的问题及解决步骤:
重复设置TokenValidationParameters
代码中两次实例化TokenValidationParameters,第二次覆盖了第一次的配置,可能引发逻辑冲突。建议合并为一次设置:options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, NameClaimType = "name", RoleClaimType = ClaimTypes.Role };ClientId配置不匹配
代码中读取的是Keycloak:ClientIdPortal,但appsettings里的配置项是Keycloak:ClientId,这会导致ClientId为空,Keycloak无法正常返回token。需要统一配置项:- 要么将代码中的
Keycloak:ClientIdPortal改为Keycloak:ClientId - 要么在appsettings中新增
ClientIdPortal配置项
- 要么将代码中的
CallbackPath与登录路径冲突
当前options.CallbackPath = "/Authenticate/Index"与Cookie的LoginPath重复,回调时可能触发重复登录逻辑,导致token处理异常。建议修改为专用回调地址,比如:options.CallbackPath = new PathString("/signin-keycloak");同时要在Keycloak客户端配置的「Valid Redirect URIs」中添加这个新地址。
手动覆盖IssuerAddress的问题
在OnRedirectToIdentityProvider事件中手动设置IssuerAddress会干扰中间件自动从Metadata获取端点的逻辑,建议注释掉这段代码://OnRedirectToIdentityProvider = async context => //{ // context.ProtocolMessage.IssuerAddress = builder.Configuration.GetValue<string>("Keycloak:Issuer"); // await Task.FromResult(0); //},检查Keycloak客户端配置
- 确保客户端「Access Type」设置为
confidential(因为使用了ClientSecret) - 确认「Valid Redirect URIs」包含应用的回调地址
- 检查「Standard Flow Enabled」是否开启(对应代码中的
ResponseType = Code)
- 确保客户端「Access Type」设置为
以下是相关配置:
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

