整合OpenIdConnect与ASP.NET Identity时如何避免无限重定向循环
我正在开发一个支持**组织邮箱(AD认证)**和普通邮箱密码登录的系统,系统登录界面提供两个选项供用户选择登录方式。
添加ASP.NET Identity前,AD认证的登录/登出功能正常,实现代码如下:
添加Identity前的工作代码
Program.cs
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(config.GetRequiredSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi( ["user.read"] ) .AddInMemoryTokenCaches(); builder.Services.Configure<OpenIdConnectOptions>( OpenIdConnectDefaults.AuthenticationScheme, options => { options.SignedOutCallbackPath = "/signout-callback-oidc"; options.SignedOutRedirectUri = "/Identity/Account/SignOut"; } );
AccountController.cs
[Route("SignIn")] [Route("[area]/[controller]/[action]")] public IActionResult SignIn() { var redirect = Url.Action("Overview", "DashboardView", new { area = "Events" }); var scheme = OpenIdConnectDefaults.AuthenticationScheme; return Challenge( new AuthenticationProperties { RedirectUri = redirect }, scheme ); } [Route("SignOut")] [Route("[area]/[controller]/[action]")] public new IActionResult SignOut() { var callbackUrl = Url.Action("LogIn", "Account", new { area = "Identity" }, protocol: Request.Scheme); return SignOut(new AuthenticationProperties { RedirectUri = callbackUrl }, CookieAuthenticationDefaults.AuthenticationScheme, OpenIdConnectDefaults.AuthenticationScheme); }
引入ASP.NET Identity后的问题
添加Identity后,代码更新如下,但出现无限重定向至SignIn方法的问题,导致用户无法通过Entra Authentication登录:
问题代码(Program.cs)
builder.Services.AddIdentity<IdentityUser, IdentityRole>() .AddEntityFrameworkStores<ActraContext>() .AddDefaultTokenProviders(); // Updated AddAuthentication for Identity builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddMicrosoftIdentityWebApp(config.GetRequiredSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi( ["user.read"] ) .AddInMemoryTokenCaches(); builder.Services.ConfigureApplicationCookie(options => { options.LoginPath = "/Identity/Account/SignIn"; options.LogoutPath = "/Identity/Account/SignOut"; options.AccessDeniedPath = ""; options.ReturnUrlParameter = "returnUrl"; });
目前尚未开发邮箱密码认证部分,已参考官方文档。
问题原因及修复方案
核心原因
引入Identity后,DefaultAuthenticateScheme设为IdentityConstants.ApplicationScheme(Identity的Cookie方案),但AD认证成功后,系统无法将OIDC认证的用户转换为Identity用户,导致后续请求仍被判定为未认证,触发LoginPath的重定向,形成循环。
修复步骤
配置OIDC回调后的用户同步逻辑
在OIDC选项中添加OnTokenValidated事件,将AD用户同步到Identity系统(创建或更新用户),并替换为Identity的ClaimsPrincipal:builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options => { options.SignedOutCallbackPath = "/signout-callback-oidc"; options.SignedOutRedirectUri = "/Identity/Account/SignOut"; options.Events.OnTokenValidated = async context => { var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>(); var email = context.Principal.FindFirstValue(ClaimTypes.Email); if (string.IsNullOrEmpty(email)) { context.Fail("Email claim not found"); return; } var user = await userManager.FindByEmailAsync(email); if (user == null) { user = new IdentityUser { UserName = email, Email = email }; var result = await userManager.CreateAsync(user); if (!result.Succeeded) { context.Fail("Failed to create user"); return; } } var identityPrincipal = await userManager.CreateUserPrincipalAsync(user); context.Principal = identityPrincipal; }; });调整SignIn方法逻辑
保留明确指定挑战方案为OIDC,同时支持传入returnUrl参数:[Route("SignIn")] [Route("[area]/[controller]/[action]")] public IActionResult SignIn(string returnUrl = null) { var redirectUrl = returnUrl ?? Url.Action("Overview", "DashboardView", new { area = "Events" }); return Challenge(new AuthenticationProperties { RedirectUri = redirectUrl }, OpenIdConnectDefaults.AuthenticationScheme); }修正Logout逻辑
登出时需同时清除Identity Cookie和OIDC会话,将原CookieAuthenticationDefaults.AuthenticationScheme替换为IdentityConstants.ApplicationScheme:[Route("SignOut")] [Route("[area]/[controller]/[action]")] public new IActionResult SignOut() { var callbackUrl = Url.Action("SignIn", "Account", new { area = "Identity" }, protocol: Request.Scheme); return SignOut( new AuthenticationProperties { RedirectUri = callbackUrl }, IdentityConstants.ApplicationScheme, OpenIdConnectDefaults.AuthenticationScheme); }确认中间件顺序
Program.cs中中间件顺序必须遵循认证在前、授权在后:app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();
内容的提问来源于stack exchange,提问作者Diego Salas

