在单个ASP.NET Core项目中切换SSO与独立用户账户认证方案
动态切换SSO与独立账户认证的实现方案
1. 同时注册两种认证方案
在Program.cs中同时配置ASP.NET Core Identity(独立账户)和Azure AD OpenID Connect(SSO),关键是禁用SSO方案的自动挑战,避免用户访问时直接重定向到Azure AD。
// 注册独立用户账户认证 builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true) .AddEntityFrameworkStores<ApplicationDbContext>(); // 注册Azure AD SSO认证,指定自定义方案名 builder.Services.AddAuthentication() .AddOpenIdConnect("AzureADSSO", options => { options.ClientId = builder.Configuration["AzureAD:ClientId"]; options.Authority = $"https://login.microsoftonline.com/{builder.Configuration["AzureAD:TenantId"]}/v2.0"; options.CallbackPath = "/signin-oidc"; options.SignedOutCallbackPath = "/signout-callback-oidc"; // 禁用自动认证和挑战,防止默认重定向 options.AutomaticAuthenticate = false; options.AutomaticChallenge = false; });
2. 实现基于组织状态的认证选择逻辑
创建登录入口,让用户提供组织标识(如域名、邮箱),后端查询SQL Server获取该组织的SSO启用状态,再引导到对应认证流程:
控制器核心代码
public class AccountController : Controller { private readonly IOrganizationRepository _orgRepository; private readonly SignInManager<ApplicationUser> _signInManager; public AccountController(IOrganizationRepository orgRepository, SignInManager<ApplicationUser> signInManager) { _orgRepository = orgRepository; _signInManager = signInManager; } // 登录入口页,让用户输入组织标识 [HttpGet] public IActionResult Login() => View(); [HttpPost] public async Task<IActionResult> SelectAuthScheme(string orgDomain) { var organization = await _orgRepository.GetByDomainAsync(orgDomain); if (organization == null) { ModelState.AddModelError("", "组织不存在"); return View("Login"); } if (organization.IsSsoEnabled) { // 触发Azure AD SSO认证 return Challenge(new AuthenticationProperties { RedirectUri = "/" }, "AzureADSSO"); } else { // 跳转到独立账户登录页 return RedirectToAction("LocalLogin"); } } // 独立账户登录逻辑 [HttpGet] public IActionResult LocalLogin() => View(); [HttpPost] public async Task<IActionResult> LocalLogin(LoginViewModel model) { if (!ModelState.IsValid) return View(model); var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false); if (result.Succeeded) return RedirectToAction("Index", "Home"); ModelState.AddModelError("", "登录失败,请检查账号密码"); return View(model); } }
3. 受保护资源的动态认证触发
如果用户直接访问受保护资源,需提前判断组织SSO状态,触发对应认证:
自定义中间件实现
public class DynamicAuthMiddleware { private readonly RequestDelegate _next; private readonly IOrganizationRepository _orgRepository; public DynamicAuthMiddleware(RequestDelegate next, IOrganizationRepository orgRepository) { _next = next; _orgRepository = orgRepository; } public async Task InvokeAsync(HttpContext context) { // 仅处理未认证用户访问受保护资源的情况 if (!context.User.Identity.IsAuthenticated && context.Request.Path.StartsWithSegments("/protected")) { // 根据系统设计获取组织标识(示例:从子域名提取) var orgDomain = context.Request.Host.Host.Split('.')[0]; var organization = await _orgRepository.GetByDomainAsync(orgDomain); if (organization?.IsSsoEnabled == true) { await context.ChallengeAsync("AzureADSSO", new AuthenticationProperties { RedirectUri = context.Request.Path }); return; } else { await context.ChallengeAsync(IdentityConstants.ApplicationScheme, new AuthenticationProperties { RedirectUri = context.Request.Path }); return; } } await _next(context); } } // 在Program.cs中注册中间件(放在UseAuthorization之前) app.UseMiddleware<DynamicAuthMiddleware>(); app.UseAuthorization();
4. 核心注意点
- 必须禁用OpenID Connect的
AutomaticAuthenticate和AutomaticChallenge,避免自动重定向 - 组织标识的获取方式需匹配你的系统架构(如邮箱域名、子域名、组织ID参数等)
- 确保两种认证方案完成后,用户身份上下文包含组织信息,方便后续权限判断
内容的提问来源于stack exchange,提问作者Avery Deemer
相关产品推荐
相关产品推荐

