为本地账户存储的Blazor Server应用添加AAD认证
Blazor Server 本地账户+Azure AD混合登录实现方案
一、调整Startup.cs中的认证配置
移除重复的Identity注册代码,添加Azure AD认证方案,实现多认证方式共存:
// Identity and Auth configuration services.AddAuthentication(options => { // 用Cookie作为默认会话存储方案,两种登录方式最终都依赖Cookie维护会话 options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; // 默认挑战方案设为Cookie,引导到统一登录页 options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) // 保留本地Cookie认证配置 .AddCookie(options => { options.Cookie.Name = "_auth"; options.Cookie.HttpOnly = true; options.LoginPath = "/Account/Login"; options.LogoutPath = "/Account/Logout"; options.AccessDeniedPath = "/Account/AccessDenied"; options.ExpireTimeSpan = TimeSpan.FromDays(1); options.SlidingExpiration = false; }) // 添加Azure AD OpenID Connect认证方案 .AddOpenIdConnect("AzureAD", options => { options.ClientId = "你的AAD应用客户端ID"; options.ClientSecret = "你的AAD应用客户端密钥"; options.Authority = "https://login.microsoftonline.com/你的租户ID/v2.0"; options.ResponseType = "code"; options.Scope.Add("openid"); options.Scope.Add("profile"); options.Scope.Add("email"); // 回调路径需与AAD应用门户中配置的一致 options.CallbackPath = "/signin-azuread"; options.SignedOutCallbackPath = "/signout-callback-azuread"; // AAD登录成功后,关联或创建本地用户 options.Events = new OpenIdConnectEvents { OnTokenValidated = async context => { var email = context.Principal.FindFirstValue(ClaimTypes.Email); if (string.IsNullOrEmpty(email)) { context.Fail("无法获取用户邮箱信息"); return; } var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>(); var localUser = await userManager.FindByEmailAsync(email); // 本地无此用户则自动创建(可根据需求改为人工审核流程) if (localUser == null) { localUser = new ApplicationUser { UserName = email, Email = email, EmailConfirmed = true }; var createResult = await userManager.CreateAsync(localUser); if (!createResult.Succeeded) { context.Fail("创建本地用户失败:" + string.Join(", ", createResult.Errors.Select(e => e.Description))); return; } } // 合并本地用户的角色与Claims到当前身份 var localClaims = await userManager.GetClaimsAsync(localUser); var identity = context.Principal.Identity as ClaimsIdentity; identity.AddClaims(localClaims); var roles = await userManager.GetRolesAsync(localUser); foreach (var role in roles) { identity.AddClaim(new Claim(ClaimTypes.Role, role)); } context.Success(); } }; }); // 配置Identity核心服务,避免重复注册 services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = false) .AddRoles<IdentityRole>() .AddRoleManager<RoleManager<IdentityRole>>() .AddSignInManager<SignInManager<ApplicationUser>>() .AddUserManager<UserManager<ApplicationUser>>() .AddEntityFrameworkStores<WebAppContext>(); // 添加授权策略,可区分不同登录方式的用户 services.AddAuthorization(options => { options.AddPolicy("LocalAccountOnly", policy => policy.RequireClaim("amr", "pwd")); options.AddPolicy("AzureADOnly", policy => policy.RequireClaim("amr", "oauth2")); });
二、修改登录页面(/Account/Login)
在原有本地登录表单基础上,添加Azure AD登录选项:
@page "/Account/Login" @inject SignInManager<ApplicationUser> SignInManager <h3>选择登录方式</h3> <!-- 本地账户登录表单 --> <form method="post"> <div class="form-group"> <label>用户名</label> <input type="text" name="UserName" class="form-control" /> </div> <div class="form-group"> <label>密码</label> <input type="password" name="Password" class="form-control" /> </div> <div class="form-group"> <input type="checkbox" name="RememberMe" /> <label class="ml-1">记住我</label> </div> <button type="submit" class="btn btn-primary">本地账户登录</button> </form> <hr /> <!-- Azure AD登录按钮 --> <form method="get" asp-action="Challenge" asp-route-scheme="AzureAD"> <button type="submit" class="btn btn-info">使用Azure AD登录</button> </form>
同时在AccountController中添加Challenge动作:
[AllowAnonymous] public IActionResult Challenge(string scheme) { return Challenge(new AuthenticationProperties { RedirectUri = "/" }, scheme); }
三、核心逻辑说明
- 多认证方案共存:同时注册Cookie、Azure AD两种认证方案,Cookie作为统一的会话存储载体,确保两种登录方式的用户都能正常访问Blazor组件。
- 用户关联机制:通过AAD用户的邮箱字段与本地用户库关联,自动创建未存在的用户(可根据业务需求改为人工审核),保证权限体系统一。
- 登录方式区分:利用
amr(认证方法参考)Claim区分登录来源,pwd代表本地密码登录,oauth2代表AAD登录,可基于此配置精细化权限策略。
四、注意事项
- 需在Azure AD门户注册应用,正确配置回调路径
/signin-azuread,并授予openid、profile、email权限。 - 本地用户与AAD用户建议通过邮箱唯一绑定,避免出现账户冲突。
- 若不允许自动创建AAD用户,可在
OnTokenValidated事件中返回失败,引导用户联系管理员创建本地账户后再登录。
内容的提问来源于stack exchange,提问作者rdm rdm
相关产品推荐
相关产品推荐

