ASP.NET MVC5使用Azure AD认证、aspnet_membership实现本地授权的问题咨询
实现方案指引
完全不需要调用Microsoft Graph,仅需在Azure AD认证回调环节插入本地Identity体系的用户匹配、声明加载逻辑即可实现需求。
具体实现步骤
- 第一步:合并项目配置
将两个项目的OWIN配置合并到Startup.Auth.cs文件中,同时保留Azure AD OpenID Connect认证配置,以及原有个人用户账户模板生成的Identity相关的DbContext、UserManager、SignInManager配置,注意保留Azure AD默认的回调路径/signin-oidc即可,二者不会产生冲突。 - 第二步:拦截OpenID Connect认证成功事件
在OpenIdConnectAuthenticationOptions的Notifications配置中,添加SecurityTokenValidated事件处理逻辑,该事件会在Azure AD返回的身份令牌验证通过后立刻触发,此时可获取Azure AD返回的用户唯一标识用于匹配本地Identity用户。
示例代码如下:
app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = "你的Azure AD应用ClientID", Authority = "https://login.microsoftonline.com/你的Azure AD租户ID", PostLogoutRedirectUri = "你的应用首页地址", Notifications = new OpenIdConnectAuthenticationNotifications { SecurityTokenValidated = async context => { // 从Azure AD返回的声明中取用户唯一标识,推荐使用oid(Azure AD用户对象ID,永久不变) var azureAdOid = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier)?.Value; var userUpn = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Upn)?.Value; // 调用原有Identity体系的UserManager var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>(); // 提前给AspNetUsers表新增AzureAdOid字段,用来存储关联的Azure AD用户oid var localUser = await userManager.Users.FirstOrDefaultAsync(u => u.AzureAdOid == azureAdOid); if (localUser == null) { // 本地无匹配用户,跳转至注册页,携带oid、upn参数用于关联 context.HandleResponse(); context.Response.Redirect($"/Account/Register?oid={azureAdOid}&upn={Uri.EscapeDataString(userUpn)}"); return; } // 本地存在匹配用户,加载本地存储的角色、自定义声明 var userClaims = await userManager.GetClaimsAsync(localUser.Id); var userRoles = await userManager.GetRolesAsync(localUser.Id); // 将本地声明写入当前登录用户的身份凭证中 var identity = context.AuthenticationTicket.Identity; identity.AddClaims(userClaims); foreach (var role in userRoles) { identity.AddClaim(new Claim(ClaimTypes.Role, role)); } } } });
- 第三步:实现本地注册逻辑
在AccountController的Register逻辑中,接收跳转带来的oid参数,用户提交注册信息后,将oid存入AspNetUsers表的对应字段,同时写入角色、自定义声明到相关表即可。
示例代码如下:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email, AzureAdOid = model.AzureAdOid, // 绑定Azure AD用户oid // 其他需要存储的用户自定义字段 }; // 无需用户输入密码的话,可以自动生成随机字符串填充密码字段 var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { // 写入用户角色 await UserManager.AddToRoleAsync(user.Id, "你的业务角色名"); // 写入自定义声明 await UserManager.AddClaimAsync(user.Id, new Claim("自定义声明类型", "对应声明值")); // 完成注册后直接登录 await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false); return RedirectToAction("Index", "Home"); } AddErrors(result); } return View(model); }
- 第四步:授权逻辑复用
原有[Authorize(Roles = "xxx")]特性、自定义授权过滤器、声明校验逻辑完全不需要修改,本地存储的角色、声明已经被写入当前登录用户的Principal对象,和原有个人用户账户模板的授权逻辑完全兼容。
注意事项
- 给AspNetUsers表新增AzureAdOid字段时建议添加唯一索引,避免同一个Azure AD用户绑定多个本地账号
- 不要使用用户邮箱作为匹配键,Azure AD用户邮箱可能发生变更,oid是唯一永久不变的用户标识,用做关联字段更稳妥
- 如果不需要保留本地密码登录能力,注册时可以自动生成随机字符串填充密码字段,用户全程只会走Azure AD登录流程
内容的提问来源于stack exchange,提问作者Ram P
相关产品推荐
相关产品推荐

