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

替换FormsAuthentication为Azure AD认证后持久化用户数据的方案咨询

解决方案:Azure AD认证后持久化本地用户数据

针对你遇到的自定义Claims不持久、重复查询本地数据库的问题,提供以下三种可行方案:


方案1:利用IClaimsTransformation将本地用户数据转为持久化Claims

通过实现ASP.NET Core的IClaimsTransformation接口,在用户认证流程中自动加载本地用户数据并添加到认证Claims中,这些Claims会被自动加密存储到Azure AD生成的认证Cookie里,跨请求自动携带。

实现步骤:

  1. 编写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;
    }
}
  1. 在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读取。

实现步骤:

  1. 在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?}");
    });
}
  1. 在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");
}
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 08:25:56