替换FormsAuthentication为Azure AD认证后持久化用户数据的方案咨询
解决方案:Azure AD认证后持久化本地用户数据
针对你遇到的自定义Claims不持久、重复查询本地数据库的问题,提供以下三种可行方案:
方案1:利用IClaimsTransformation将本地用户数据转为持久化Claims
通过实现ASP.NET Core的IClaimsTransformation接口,在用户认证流程中自动加载本地用户数据并添加到认证Claims中,这些Claims会被自动加密存储到Azure AD生成的认证Cookie里,跨请求自动携带。
实现步骤:
- 编写Claims转换服务:
public class LocalUserClaimsTransformer : IClaimsTransformation { private readonly ILocalUserService _localUserService; public LocalUserClaimsTransformer(ILocalUserService localUserService) { _localUserService = localUserService; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { // 仅在用户已认证且无本地用户Claims时加载数据 if (principal.Identity.IsAuthenticated && !principal.HasClaim(c => c.Type == "LocalUserId")) { var aadUserId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; var localUser = await _localUserService.GetByAadUserIdAsync(aadUserId); if (localUser != null) { var identity = (ClaimsIdentity)principal.Identity; identity.AddClaim(new Claim("LocalUserId", localUser.Id.ToString())); identity.AddClaim(new Claim("Permissions", string.Join(",", localUser.Permissions))); // 按需添加其他本地用户字段(如角色、部门等) } } return principal; } }
- 在Startup中注册服务并配置Cookie认证:
public void ConfigureServices(IServiceCollection services) { // 注册Claims转换服务 services.AddScoped<IClaimsTransformation, LocalUserClaimsTransformer>(); services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")) .AddCookie(options => { options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromHours(8); // 匹配业务会话时长 }); }
优点:
- 完全贴合ASP.NET Core认证模型,无需额外存储组件
- Claims随认证Cookie自动持久化,跨请求直接读取
- 自动处理Claims的加密与解密
方案2:使用Session存储本地用户完整数据
如果本地用户数据量较大(不适合存入Claims),可启用ASP.NET Session,首次认证后将用户数据存入Session,后续请求直接从Session读取。
实现步骤:
- 在Startup中启用Session:
public void ConfigureServices(IServiceCollection services) { services.AddSession(options => { options.IdleTimeout = TimeSpan.FromHours(8); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 其他服务配置... } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // 注意中间件顺序:Session需在认证、MVC之前 app.UseSession(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); }
- 在Azure AD回调Action中存入Session:
public async Task<IActionResult> Callback() { var authResult = await HttpContext.AuthenticateAsync(OpenIdConnectDefaults.AuthenticationScheme); if (authResult.Succeeded) { var aadUserId = authResult.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; var localUser = await _localUserService.GetByAadUserIdAsync(aadUserId); if (localUser != null) { HttpContext.Session.SetString("LocalUser", JsonSerializer.Serialize(localUser)); } } return RedirectToAction("Index", "Home"); }
- 在Controller/View中读取Session:
public IActionResult Dashboard() { var localUserJson = HttpContext.Session.GetString("LocalUser"); var localUser = JsonSerializer.Deserialize<LocalUser>(localUserJson); // 使用本地用户数据 return View(localUser); }
优点:
- 适合存储大量用户数据,不会增大认证Cookie体积
- 实现简单,无需修改认证核心逻辑
注意:
- 集群部署时需配置Session共享(如Redis分布式Session)
方案3:在认证SigningIn事件中手动添加Claims
直接在Cookie认证的OnSigningIn事件中加载本地用户数据并添加到认证Ticket,确保Claims被写入Cookie持久化。
实现代码:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")) .AddCookie(options => { options.Events.OnSigningIn = async context => { var principal = context.Principal; var aadUserId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; var localUser = await _localUserService.GetByAadUserIdAsync(aadUserId); if (localUser != null) { var identity = (ClaimsIdentity)principal.Identity; identity.AddClaim(new Claim("LocalUserId", localUser.Id.ToString())); identity.AddClaim(new Claim("Role", localUser.Role)); // 添加其他需要持久化的字段 } }; });
优点:
- 精准控制认证流程中的Claims添加时机
- 无需额外注册服务,配置更简洁
前端AngularJS适配建议
如果前端需要使用本地用户数据,可在Layout页面中将Claims渲染为全局变量:
<script> window.localUser = { id: '@User.FindFirst("LocalUserId")?.Value', permissions: '@User.FindFirst("Permissions")?.Value'.split(',') }; </script>
AngularJS可直接读取window.localUser,避免重复调用后端API。
内容的提问来源于stack exchange,提问作者hrishi
相关产品推荐
相关产品推荐

