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

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分钟就会自动失效。
修复方案
  1. 删除手动编写的AddAuthentication()代码块,不要单独调用AddCookie()注册认证方案,直接在AddDefaultIdentity()的调用链上追加OIDC配置,复用Identity默认的Cookie认证体系,避免方案冲突。需要调整Cookie过期时间等参数时,直接在AddDefaultIdentity的配置选项中设置即可。
  2. 在OIDC的OnTokenValidated事件中补充本地用户匹配/创建逻辑,调用SignInManager的相关方法为用户签发Identity认可的登录凭证,将认证上下文的Principal替换为Identity生成的用户身份对象。
  3. 删除重复注册的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 00:48:19