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

为本地账户存储的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);
}

三、核心逻辑说明

  1. 多认证方案共存:同时注册Cookie、Azure AD两种认证方案,Cookie作为统一的会话存储载体,确保两种登录方式的用户都能正常访问Blazor组件。
  2. 用户关联机制:通过AAD用户的邮箱字段与本地用户库关联,自动创建未存在的用户(可根据业务需求改为人工审核),保证权限体系统一。
  3. 登录方式区分:利用amr(认证方法参考)Claim区分登录来源,pwd代表本地密码登录,oauth2代表AAD登录,可基于此配置精细化权限策略。

四、注意事项

  • 需在Azure AD门户注册应用,正确配置回调路径/signin-azuread,并授予openid、profile、email权限。
  • 本地用户与AAD用户建议通过邮箱唯一绑定,避免出现账户冲突。
  • 若不允许自动创建AAD用户,可在OnTokenValidated事件中返回失败,引导用户联系管理员创建本地账户后再登录。

内容的提问来源于stack exchange,提问作者rdm rdm

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 19:45:42