ASP.NET Core集成MS Identity与OIDC时OIDC登出失效问题
现有遗留方案通过Microsoft Identity对接本地SQL库实现用户认证,现需访问由Open ID Connect(OIDC)保护的第三方API,其余功能正常,但用户登出API时,OIDC登出流程未触发(未跳转至OIDC登出页面)。
使用的Startup.cs代码片段如下:
public void ConfigureServices(IServiceCollection services) { services.AddIdentity<AppUser, IdentityRole>() .AddEntityFrameworkStores<AppIdentityDbContext>() .AddDefaultTokenProviders(); services.ConfigureApplicationCookie(options => { options.LoginPath = "/Account/LogIn"; options.LogoutPath = "/Account/LogOut"; options.AccessDeniedPath = "/Account/AccessDenied"; }); services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; // "Cookies" options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; // "OpenIdConnect" }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = Configuration["Jwt:Issuer"], ValidAudience = Configuration["Jwt:Issuer"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"])) }; }) .AddCookie(setup => setup.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime)) .AddOpenIdConnect(options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.Authority = identityUrl; options.ClientId = clientId; options.ClientSecret = clientSecret; options.ResponseType = OpenIdConnectResponseType.Code; options.SaveTokens = true; options.UsePkce = true; options.RequireHttpsMetadata = false; options.Scope.Clear(); options.Scope.Add("openid"); options.SignedOutRedirectUri = "/Home/Index"; }); } public async Task Login() { await HttpContext.ChallengeAsync(OpenIdConnectDefaults.AuthenticationScheme); // "OpenIdConnect" } public async Task Logout() { await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme); // "OpenIdConnect" //await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); // "Cookies" }
当前使用Microsoft.AspNetCore.Authentication.OpenIdConnect 5.0.17版本,尝试开启IdentityModelEventSource.ShowPII未生效,搭建仅含OIDC认证的示例应用通过Wireshark抓包,但因HTTPS无法解密流量。
需明确两个问题:
- OIDC登出失效的原因是什么?
- 这种双重认证场景是否可行?
OIDC登出失效的原因
Cookie认证方案冲突
代码中同时调用services.AddIdentity()和services.AddAuthentication().AddCookie(),导致注册了两个同名的Cookies认证方案。AddIdentity()内部已默认注册Identity应用Cookie,手动再调用AddCookie()会造成覆盖或关联混乱,使得OIDC登出时无法正确匹配到对应的本地Cookie,进而无法触发跳转至OIDC提供商的登出流程。登出逻辑不完整
现有Logout方法仅调用SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme),但OIDC登出需要先清除本地认证Cookie,再触发OIDC提供商的登出端点跳转。缺少本地Cookie清除步骤,会导致OIDC登出流程无法正常触发。OIDC配置缺失关键参数
现有OIDC配置未设置PostLogoutRedirectUri(部分OIDC提供商要求该参数确认登出后的跳转地址),也未配置SignedOutCallbackPath及相关事件处理登出后的回调逻辑,影响登出流程的完整性。
双重认证场景的可行性
这种混合认证场景(本地Identity + OIDC + JWT Bearer)完全可行,是遗留系统对接外部认证资源的常见方案。只要正确配置各认证方案的优先级、名称关联关系,就能实现本地用户认证、OIDC第三方登录/登出、JWT API授权的共存。
修复步骤
移除重复的Cookie认证注册
删除services.AddAuthentication().AddCookie(...)代码行,通过ConfigureApplicationCookie统一配置Identity应用Cookie的过期时间:services.ConfigureApplicationCookie(options => { options.LoginPath = "/Account/LogIn"; options.LogoutPath = "/Account/LogOut"; options.AccessDeniedPath = "/Account/AccessDenied"; options.ExpireTimeSpan = TimeSpan.FromMinutes(sessionCookieLifetime); // 在此设置Cookie过期时间 });完善登出逻辑
修改Logout方法,同时清除本地Cookie并触发OIDC登出跳转:public async Task Logout() { // 清除本地Identity认证Cookie await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); // 触发OIDC提供商登出,并指定跳转地址 await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/Home/Index" }); }补充OIDC登出相关配置
在AddOpenIdConnect配置中添加以下参数:options.PostLogoutRedirectUri = "/Home/Index"; options.SignedOutCallbackPath = "/Account/SignedOut"; // 用于处理OIDC登出后的回调 options.Events.OnSignedOutCallbackRedirect = context => { context.Response.Redirect(context.Options.SignedOutRedirectUri); context.HandleResponse(); return Task.CompletedTask; };正确开启PII日志排查
在Program.cs(或Startup的Configure方法开头)添加代码开启PII日志,并调整日志级别配置:IdentityModelEventSource.ShowPII = true;同时在
appsettings.json中配置日志级别:"Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning", "Microsoft.AspNetCore.Authentication": "Debug" } }
内容的提问来源于stack exchange,提问作者Peter

