ASP.NET Core 3.1 OpenIDConnect登录后IsSignedIn始终返回false
问题场景
本问题针对ASP.NET Core 3.x(MVC6,非旧版MVC5)+ EF Core技术栈的应用,该应用使用OpenID Connect实现登录安全与授权能力。
异常现象
用户可正常通过OpenID身份提供方完成校验,用户数据也可正常写入EF Core关联的数据库,但完成登录流程跳转至站点默认页后,局部视图无法识别用户已登录状态:_LoginPartial.cshtml中调用SignInManager.IsSignedIn(User)始终返回false,导致其他页面无法获取用户名、邮箱等用户身份信息。
初步怀疑问题出在Program.cs的认证配置环节,存在未将当前登录用户身份正确传递给系统注入的UserManager/SignInManager的问题。
相关代码
_LoginPartial.cshtml 代码片段
@using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.Mvc.TagHelpers @inject SignInManager<IdentityUser> SignInManager @inject UserManager<IdentityUser> UserManager <ul class="navbar-nav"> @if (SignInManager.IsSignedIn(User)) { // 始终无法识别用户已登录 // 尽管已通过外部服务完成登录并跳转至默认页面
Program.cs 配置代码
using System.IdentityModel.Tokens.Jwt; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Newtonsoft.Json.Serialization; using MyApplication.Data; bool useAuthentication = true; MyApplication.Common.AppConfig.AddOrUpdate("config:args",args); var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection"); builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString)); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); builder.Services.AddControllersWithViews(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddControllers() .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new DefaultContractResolver(); }); if (useAuthentication) { builder.Services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(options=>options.ExpireTimeSpan = TimeSpan.FromMinutes(1)) .AddOpenIdConnect(options => { options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.ClientId = builder.Configuration["OpenID:ClientID"]; options.ClientSecret = builder.Configuration["OpenID:ClientSecret"]; options.Authority = builder.Configuration["OpenID:Authority"]; options.CallbackPath = builder.Configuration["OpenID:CallbackPath"]; options.ResponseType = OpenIdConnectResponseType.Code; options.ClaimActions.MapUniqueJsonKey("username", "username"); options.Events = new OpenIdConnectEvents { OnTokenValidated = tokencontext => { // 可在此处处理当前用户逻辑 // 但用户数据已正常写入EF Core,是否仍需要在此处补充逻辑? return Task.CompletedTask; }, OnTicketReceived = context => { // 若存在基于用户的认证逻辑可在此处添加 return Task.CompletedTask; }, OnAuthenticationFailed = context => { context.Response.Redirect("/Home/Error"); context.HandleResponse(); // 屏蔽异常信息 return Task.CompletedTask; }, }; }); } var app = builder.Build(); using (var scope = app.Services.CreateScope()) { var services = scope.ServiceProvider; var context = services.GetRequiredService<ApplicationDbContext>(); context.Database.Migrate(); } if (app.Environment.IsDevelopment()) { app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=MyApplication}/{action=Index}/{id?}").RequireAuthorization(); }); app.MapControllerRoute( name: "default", pattern: "{controller=MyApplication}/{action=Index}/{id?}"); app.MapControllerRoute(name: "api", pattern: "api/{controller=Api}/{Action=Test}/{id?}/{country?}"); app.MapRazorPages(); app.Run();
问题根因
三个核心配置错误导致登录状态无法被识别:
- 手动调用
AddAuthentication()配置Cookie和OIDC的操作,覆盖了AddDefaultIdentity()默认注册的Identity认证方案。相当于新建了一套独立的Cookie认证体系,和SignInManager依赖的Identity默认Cookie体系完全隔离,OIDC登录写入的Cookie不在SignInManager的识别范围内。 - OIDC流程仅完成了外部身份校验,未调用SignInManager的方法为用户签发Identity认可的登录凭证。OIDC中间件自行写入的Cookie缺少Identity校验登录状态所需的声明,
SignInManager.IsSignedIn()判定逻辑自然一直返回false。 - 重复注册了两次同名的default路由:第一条带
RequireAuthorization(),第二条无授权要求,规则冲突会导致部分请求绕过授权校验流程,读不到正确的用户身份信息。 - 额外问题:手动配置的Cookie过期时间仅为1分钟,就算其他配置正确,登录后1分钟就会自动失效。
修复方案
- 删除手动编写的
AddAuthentication()代码块,不要单独调用AddCookie()注册认证方案,直接在AddDefaultIdentity()的调用链上追加OIDC配置,复用Identity默认的Cookie认证体系,避免方案冲突。需要调整Cookie过期时间等参数时,直接在AddDefaultIdentity的配置选项中设置即可。 - 在OIDC的
OnTokenValidated事件中补充本地用户匹配/创建逻辑,调用SignInManager的相关方法为用户签发Identity认可的登录凭证,将认证上下文的Principal替换为Identity生成的用户身份对象。 - 删除重复注册的default路由规则,所有路由统一在一个
UseEndpoints块中注册,避免规则冲突。
修复后的核心代码示例
// 替换原有AddDefaultIdentity和AddAuthentication相关代码 builder.Services.AddDefaultIdentity<IdentityUser>(options => { options.SignIn.RequireConfirmedAccount = true; // 在此处调整Cookie过期时间等配置,不要单独AddCookie }) .AddEntityFrameworkStores<ApplicationDbContext>() .AddOpenIdConnect(options => // 直接在Identity配置链上追加OIDC { options.ClientId = builder.Configuration["OpenID:ClientID"]; options.ClientSecret = builder.Configuration["OpenID:ClientSecret"]; options.Authority = builder.Configuration["OpenID:Authority"]; options.CallbackPath = builder.Configuration["OpenID:CallbackPath"]; options.ResponseType = OpenIdConnectResponseType.Code; options.ClaimActions.MapUniqueJsonKey("username", "username"); options.Events = new OpenIdConnectEvents { OnTokenValidated = async context => { var signInManager = context.HttpContext.RequestServices .GetRequiredService<SignInManager<IdentityUser>>(); var userManager = signInManager.UserManager; // 从OIDC返回的声明中提取用户标识,可根据IdP实际返回字段调整 var oidcUserId = context.Principal.FindFirstValue("sub"); var oidcEmail = context.Principal.FindFirstValue("email"); var oidcUsername = context.Principal.FindFirstValue("username"); // 查找本地对应用户,不存在则自动创建 var user = await userManager.FindByLoginAsync("OpenIdConnect", oidcUserId); if (user == null) { user = new IdentityUser { UserName = oidcUsername ?? oidcEmail, Email = oidcEmail }; var createResult = await userManager.CreateAsync(user); if (!createResult.Succeeded) { context.Fail("本地用户创建失败"); return; } await userManager.AddLoginAsync(user, new UserLoginInfo("OpenIdConnect", oidcUserId, oidcUsername)); } // 生成Identity认可的身份凭证,完成登录 var claimsPrincipal = await signInManager.CreateUserPrincipalAsync(user); context.Principal = claimsPrincipal; await signInManager.SignInAsync(user, isPersistent: false); }, OnAuthenticationFailed = context => { context.Response.Redirect("/Home/Error"); context.HandleResponse(); return Task.CompletedTask; } }; }); // 中间件顺序保持UseHttpsRedirection -> UseStaticFiles -> UseRouting -> UseAuthentication -> UseAuthorization 不变 // 替换原有路由配置,删除重复注册的规则 app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=MyApplication}/{action=Index}/{id?}").RequireAuthorization(); endpoints.MapControllerRoute(name: "api", pattern: "api/{controller=Api}/{Action=Test}/{id?}/{country?}"); endpoints.MapRazorPages(); }); // 删掉后面重复写的MapControllerRoute、MapRazorPages代码
内容的提问来源于stack exchange,提问作者Peter
相关产品推荐
相关产品推荐

