容器化ASP.NET Core MVC应用Chrome登录重定向及K8多Pod异常问题
问题背景
将基于ASP.NET Core MVC的Web应用容器化并部署到K8s集群,使用Azure AD认证时遭遇以下问题:
- 已在
Startup.cs中配置HTTPS重定向,但Chrome/Edge会重定向至/signin-oidc页面,报Correlation failed错误 - Firefox中应用虽能运行但不稳定,偶尔报Unable to unprotect the message.State错误
- 添加
CookiePolicy配置解决了Chrome的问题,但K8s集群使用2个Pod时,请求分发到不同Pod仍出现Unable to unprotect the message.State错误
核心代码片段
Startup.cs ConfigureServices方法
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(AzureADDefaults.AuthenticationScheme) .AddAzureAD(options => Configuration.Bind("AzureAd", options)); services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, (OpenIdConnectOptions options) => { options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.Events.OnAuthorizationCodeReceived = OnAuthorizationCodeReceived; options.Events.OnRedirectToIdentityProvider = async context => { string redirectUri = "https://" + context.Request.Host + "/signin-oidc";; AzureAdAppSettingsHelper.RedirectUriSuffix; context.ProtocolMessage.RedirectUri = redirectUri; context.ProtocolMessage.PostLogoutRedirectUri = redirectUri; context.Response.Redirect(redirectUri); }; }); var redisConnectionString = Environment.GetEnvironmentVariable("redcache"); _redis = ConnectionMultiplexer.Connect(redisConnectionString); services.AddHttpContextAccessor(); services.AddControllersWithViews(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }).AddJsonOptions(jsonOptions => jsonOptions.JsonSerializerOptions.PropertyNamingPolicy = null); services.AddKendo(); services.AddRazorPages(); services.AddScoped<ICacheService, CacheService>(); services.AddApplicationInsightsTelemetry(Configuration["APPINSIGHTS_CONNECTIONSTRING"]); }
Startup.cs Configure方法
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Err"); app.UseHsts(); } app.UseDefaultFiles(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapRazorPages(); endpoints.MapControllerRoute(name: "Ins", pattern: "Ins/{*Home}", defaults: new { controller = "ins", action = "Home" }); }); }
OnAuthorizationCodeReceived方法
private async static Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext ctx) { var claims = new ClaimsPrincipal(); const string AZUREADGRAPH_RESOURCE = @"https://graph.windows.net/"; var credential = new ClientCredential(ctx.Options.ClientId, ctx.Options.ClientSecret); string userId = ctx.Principal.FindFirst(ClaimTypes.NameIdentifier).Value; var cache = new adowebhub.Controllers.RedisTokenCache(ctx.Options.ClientId, userId); var authContext = new AuthenticationContext(ctx.Options.Authority, cache); var result = await authContext.AcquireTokenByAuthorizationCodeAsync(ctx.ProtocolMessage.Code, new Uri(ctx.TokenEndpointRequest.RedirectUri), credential, resource: AZUREADGRAPH_RESOURCE); ctx.HandleCodeRedemption(result.AccessToken, result.IdToken); }
报错信息
Chrome环境
System.Exception: An error was encountered while handling the remote login.
System.Exception: Correlation failed.
Firefox环境(偶尔出现)
System.Exception: An error was encountered while handling the remote login.
System.Exception: Unable to unprotect the message.State.
多Pod环境(添加CookiePolicy后)
System.Exception: An error was encountered while handling the remote login.
System.Exception: Unable to unprotect the message.State.
已尝试的解决方法
添加CookiePolicy配置解决了Chrome的Correlation失败问题:
app.UseCookiePolicy(new CookiePolicyOptions { Secure = CookieSecurePolicy.Always });
解决方案建议
1. 统一多Pod的Data Protection密钥
ASP.NET Core的Data Protection系统用于加密/解密认证相关的message.State,多Pod环境下每个Pod默认使用独立的密钥,导致跨Pod无法解密。利用现有Redis连接配置共享存储:
var redis = ConnectionMultiplexer.Connect(redisConnectionString); services.AddDataProtection() .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys") .SetApplicationName("YourAppName"); // 所有Pod必须使用相同的应用名称
2. 修正OpenIdConnect重定向逻辑
当前OnRedirectToIdentityProvider事件中直接调用context.Response.Redirect(redirectUri)会中断OIDC正常流程,移除该行代码,让中间件自行处理:
options.Events.OnRedirectToIdentityProvider = async context => { string redirectUri = "https://" + context.Request.Host + "/signin-oidc"; // 移除无效的AzureAdAppSettingsHelper.RedirectUriSuffix引用 context.ProtocolMessage.RedirectUri = redirectUri; context.ProtocolMessage.PostLogoutRedirectUri = redirectUri; // 移除 context.Response.Redirect(redirectUri); await Task.CompletedTask; };
3. 完善Cookie配置
添加SameSite属性避免跨站Cookie问题,根据业务场景调整取值:
app.UseCookiePolicy(new CookiePolicyOptions { Secure = CookieSecurePolicy.Always, SameSite = SameSiteMode.Lax, // 跨站场景可设为None,但必须配合Secure=true HttpOnly = true });
4. 临时配置K8s会话亲和性
若暂时无法配置Data Protection共享密钥,可在K8s Service中设置会话亲和性,让同一用户请求固定分发到同一Pod:
apiVersion: v1 kind: Service metadata: name: your-app-service spec: selector: app: your-app ports: - protocol: TCP port: 80 targetPort: 80 sessionAffinity: ClientIP sessionAffinityConfig: clientIP: timeoutSeconds: 10800 # 3小时
注:此为临时方案,长期需配置共享Data Protection密钥。
5. 验证Azure AD应用重定向URI
确保Azure AD应用注册中已添加https://<your-domain>/signin-oidc作为重定向URI,且与代码配置完全一致。
内容的提问来源于stack exchange,提问作者Piyush

