ASP.NET Core 8中基于用户名动态切换Azure AD B2C自定义策略登录
解决方案:ASP.NET Core 8 + Azure AD B2C 动态策略统一登录页
核心思路
通过调整认证流程起始逻辑,让未认证用户先进入自定义统一登录页输入用户名,再根据邮箱域名判断触发对应Azure AD B2C自定义策略的认证挑战,而非直接跳转至Azure。这并非Microsoft.Identity.Web的限制,而是需要修改默认的认证跳转规则。
步骤1:配置多策略认证方案
在Program.cs中注册员工/客户两个B2C自定义策略的认证方案,同时修改Cookie认证的登录路径指向自定义页:
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); // 添加MVC和Razor Pages服务 builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); // 注册两个B2C自定义策略的认证方案 builder.Services.AddAuthentication() // 员工专属认证方案:"EmployeePolicy" .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAdB2C:Employee", options); }, authenticationScheme: "EmployeePolicy", cookieScheme: "EmployeeCookie") // 客户专属认证方案:"CustomerPolicy" .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAdB2C:Customer", options); }, authenticationScheme: "CustomerPolicy", cookieScheme: "CustomerCookie"); // 配置Cookie中间件,将未认证用户引导至自定义登录页 builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.LoginPath = "/Account/SelectLogin"; }); // 配置每个策略的OpenIdConnect事件,传递login_hint参数到B2C builder.Services.Configure<OpenIdConnectOptions>("EmployeePolicy", options => { options.Events.OnRedirectToIdentityProvider = context => { if (context.Properties.Items.TryGetValue("login_hint", out var loginHint)) { context.ProtocolMessage.LoginHint = loginHint; } return Task.CompletedTask; }; }); builder.Services.Configure<OpenIdConnectOptions>("CustomerPolicy", options => { options.Events.OnRedirectToIdentityProvider = context => { if (context.Properties.Items.TryGetValue("login_hint", out var loginHint)) { context.ProtocolMessage.LoginHint = loginHint; } return Task.CompletedTask; }; }); var app = builder.Build(); // 中间件配置 if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.MapRazorPages(); app.Run();
步骤2:创建自定义登录页控制器
添加AccountController,提供允许匿名访问的登录选择页,处理用户名输入并动态触发对应策略的认证:
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Identity.Web; [AllowAnonymous] public class AccountController : Controller { private readonly IOptionsSnapshot<MicrosoftIdentityOptions> _optionsSnapshot; public AccountController(IOptionsSnapshot<MicrosoftIdentityOptions> optionsSnapshot) { _optionsSnapshot = optionsSnapshot; } [HttpGet] public IActionResult SelectLogin(string returnUrl = null) { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost] public IActionResult SelectLogin(string username, string returnUrl = null) { if (string.IsNullOrWhiteSpace(username)) { ModelState.AddModelError("", "请输入用户名"); ViewData["ReturnUrl"] = returnUrl; return View(); } // 根据邮箱域名判断使用的策略(示例:员工邮箱后缀为@company.com) string authScheme = username.EndsWith("@company.com", StringComparison.OrdinalIgnoreCase) ? "EmployeePolicy" : "CustomerPolicy"; // 构造认证属性,传递login_hint和原返回URL var authProperties = new AuthenticationProperties { RedirectUri = returnUrl ?? Url.Content("~/"), Items = { { "login_hint", username } } }; // 触发对应策略的认证挑战 return Challenge(authProperties, authScheme); } }
步骤3:创建登录页视图
在Views/Account目录下添加SelectLogin.cshtml,实现简单的用户名输入表单:
@{ ViewData["Title"] = "统一登录"; } <div class="container mt-5"> <h2>统一登录</h2> <form method="post" class="mt-3"> <div class="mb-3"> <label for="username" class="form-label">用户名(邮箱)</label> <input type="email" class="form-control" id="username" name="username" required> </div> <input type="hidden" name="returnUrl" value="@ViewData["ReturnUrl"]"> <button type="submit" class="btn btn-primary">下一步</button> </form> </div>
关键说明
为什么之前的方法无效?
- 默认使用OpenIdConnect作为认证方案时,未认证用户会直接触发OpenIdConnect的
Challenge跳转至Azure。通过修改Cookie的LoginPath,我们将初始跳转拦截到自定义页,再手动触发对应策略的认证流程。 RedirectToIdentityProvider事件是在进入OpenIdConnect流程后触发的,无法拦截初始的未认证跳转。
- 默认使用OpenIdConnect作为认证方案时,未认证用户会直接触发OpenIdConnect的
页面保护逻辑
- 所有业务页面仍保留
[Authorize]属性,未认证用户访问时会被Cookie中间件引导至/Account/SelectLogin(该页已配置[AllowAnonymous])。
- 所有业务页面仍保留
配置文件参考
在appsettings.json中添加两个策略的配置:"AzureAdB2C": { "Employee": { "Instance": "https://yourtenant.b2clogin.com/", "Domain": "yourtenant.onmicrosoft.com", "TenantId": "你的租户ID", "ClientId": "员工端应用ClientID", "CallbackPath": "/signin-oidc-employee", "SignUpSignInPolicyId": "B2C_1A_Employee_SignIn" }, "Customer": { "Instance": "https://yourtenant.b2clogin.com/", "Domain": "yourtenant.onmicrosoft.com", "TenantId": "你的租户ID", "ClientId": "客户端应用ClientID", "CallbackPath": "/signin-oidc-customer", "SignUpSignInPolicyId": "B2C_1A_Customer_SignIn" } }
内容的提问来源于stack exchange,提问作者Mythprod
相关产品推荐
相关产品推荐

