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

ASP.NET Core 6 MVC+OpenID+Docker环境下关联失败问题排查

问题:ASP.NET Core 6 MVC + Keycloak OpenID 关联错误与间歇性登录问题

部署在Docker容器中的ASP.NET Core 6 MVC应用,对接Keycloak OpenID时出现登录/登出关联错误,以及间歇性登录失效问题——成功登录后过几分钟打开同一URL就无法登录,怀疑是Cookie丢失、找不到或不匹配。

错误日志

warn: Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConnectHandler[15]
      '.AspNetCore.Correlation.XxYMZvzzXbtsXlUrwh1xD-uGyn24-skm69u0SIkk0LU' cookie not found.
fail: Microsoft.AspNetCore.Server.Kestrel[13]
      Connection id "0HMP8EE71Q7FF", Request id "0HMP8EE71Q7FF:00000003": An unhandled exception was thrown by the application.
      System.Exception: An error was encountered while handling the remote login.
       ---> System.Exception: Correlation failed.
         --- End of inner exception stack trace ---
         at Microsoft.AspNetCore.Authentication.RemoteAuthenticationHandler`1.HandleRequestAsync()
         at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Session.SessionMiddleware.Invoke(HttpContext context)
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

现有Program.cs配置

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    options.DefaultAuthenticateScheme = "oidc";
    options.DefaultSignInScheme = "Cookies";
})
.AddCookie(options =>
{
    // add an instance of the patched manager to the options:
    options.CookieManager = new ChunkingCookieManager();

    options.Cookie.HttpOnly = true;
    options.Cookie.SameSite = SameSiteMode.None;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
})
.AddOpenIdConnect(options =>
{
    options.Authority = serverURL + "/realms/" + realm;
    options.ClientId = clientTd;
    options.ClientSecret = secret;
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.ResponseMode = "query";
    options.UsePkce = false;
    options.RequireHttpsMetadata = false;
    options.NonceCookie.SameSite = SameSiteMode.None;
    options.CorrelationCookie.SameSite = SameSiteMode.None;

    options.Events.OnAuthorizationCodeReceived = authorizationCtx =>
    {
        // access code thru authorizationCtx.TokenEndpointRequest.Code
        return Task.FromResult(0);
    };
    options.Events.OnTokenResponseReceived = ctx => {
        KeycloackAdapter.HandleToken(
            ctx.TokenEndpointResponse.AccessToken,
            ctx.TokenEndpointResponse.RefreshToken, 
            ctx.TokenEndpointResponse.IdToken, 
            ctx.HttpContext
        );

        return Task.FromResult(0);
    };

    options.Events.OnUserInformationReceived = userInfo =>
    {
        return Task.FromResult(0);
    };
});

services.AddAuthorization(options =>
{
    options.AddPolicy("Keycloak", policy =>
        policy.Requirements.Add(new KeycloakPolicyRequirement()));
});

services.AddTransient<IAuthorizationHandler, KeycloakPolicyHandler>();

services.AddMvc().AddNewtonsoftJson(o =>
{
    // prevent JSON from changing to camelCase
    o.UseMemberCasing();
});

services.AddDataProtection().PersistKeysToFileSystem(
    new DirectoryInfo(Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "DataProtection"));

services.AddDataProtection().UseCryptographicAlgorithms(
    new AuthenticatedEncryptorConfiguration
    {
        EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
        ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
    });

services.AddRazorPages().AddSessionStateTempDataProvider();
services.AddControllersWithViews().AddSessionStateTempDataProvider();

services.AddSession(options => {
    options.IdleTimeout = TimeSpan.FromHours(12);
    options.Cookie.IsEssential = true;
});

services.AddHttpContextAccessor();

var app = builder.Build();

app.UseCookiePolicy();
app.UseHttpsRedirection();
app.UseHangfireDashboard();

app.UseCookiePolicy(new CookiePolicyOptions()
{
    MinimumSameSitePolicy = SameSiteMode.None,
    Secure = CookieSecurePolicy.Always,
});

app.UseStaticFiles(new StaticFileOptions
{
    ServeUnknownFileTypes = true
});

app.UseSession();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.UseDeveloperExceptionPage();

排查与修复建议

1. 修复Data Protection配置问题

  • 两次调用services.AddDataProtection()会覆盖前序配置,合并为一次调用:
    services.AddDataProtection()
        .PersistKeysToFileSystem(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), "DataProtection")))
        .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration
        {
            EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC,
            ValidationAlgorithm = ValidationAlgorithm.HMACSHA256
        });
    
  • 确认Docker挂载的DataProtection目录权限:容器内应用进程需拥有读写该目录的权限,否则无法持久化密钥,导致每次重启后密钥重置,已加密Cookie无法解密。
  • 多实例部署时,确保所有容器共享同一套Data Protection密钥,否则不同实例生成的Cookie无法互相识别。

2. 修正Authentication Scheme冲突

  • 当前配置中DefaultAuthenticateScheme设为oidc,与DefaultScheme、DefaultSignInScheme的Cookie Scheme存在潜在冲突,统一配置:
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    
  • 在AddOpenIdConnect中显式指定SignInScheme为Cookie Scheme:
    options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    

3. 优化Cookie配置与中间件顺序

  • 两次调用app.UseCookiePolicy()会覆盖配置,保留一次并调整位置到UseHttpsRedirection之后:
    app.UseHttpsRedirection();
    app.UseCookiePolicy(new CookiePolicyOptions()
    {
        MinimumSameSitePolicy = SameSiteMode.None,
        Secure = CookieSecurePolicy.Always,
    });
    
  • 为Correlation和Nonce Cookie添加SecurePolicy,确保仅通过HTTPS传输:
    options.CorrelationCookie.SecurePolicy = CookieSecurePolicy.Always;
    options.NonceCookie.SecurePolicy = CookieSecurePolicy.Always;
    
  • 若应用与Keycloak不在同一域名下,为Cookie指定正确Domain,避免浏览器拒绝存储跨域Cookie:
    options.Cookie.Domain = ".yourdomain.com"; // 替换为实际域名
    

4. 排查Docker网络与反向代理问题

  • 若前端有反向代理(如Nginx),添加ForwardedHeaders配置,确保ASP.NET Core识别HTTPS请求:
    app.UseForwardedHeaders(new ForwardedHeadersOptions
    {
        ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
    });
    
  • 同步Docker容器与Keycloak服务器时间:时间不一致会导致令牌或Cookie提前失效,引发间歇性登录问题。

5. 调试Cookie传输过程

  • 开启浏览器开发者工具(F12),在Application标签页查看:
    • .AspNetCore.Correlation.*、.AspNetCore.OpenIdConnect.Nonce.*及Cookie Scheme对应的Cookie是否存在。
    • Cookie的SameSite、Secure、HttpOnly属性是否符合配置。
    • Cookie的Domain和Path是否正确。
  • 在OnTokenResponseReceived事件中添加日志,记录当前HttpContext的Cookie信息,确认Cookie是否被正确设置。

6. 修正中间件顺序

  • 中间件顺序错误会导致Cookie读取失败,确保顺序如下:
    app.UseForwardedHeaders(); // 有反向代理时添加
    app.UseHttpsRedirection();
    app.UseCookiePolicy();
    app.UseStaticFiles();
    app.UseSession();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseDeveloperExceptionPage();
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 21:25:00