如何将 Blazor Identity 脚手架与我自己的数据库上下文松散耦合?

How do I loosely couple the Blazor Identity scaffold with my own Database Context?

我创建了一个 Blazor 服务器应用程序,可以选择搭建身份系统。这创建了一个 Entity Framework IdentityDbContext,其中包含许多用于管理用户登录和设置的表。我决定将我自己的 DbContext 与此分开,以便以后可以在必要时替换其中任何一个上下文。

我想做的是在我自己的自定义 dbcontext 中有一个用户实体,并在其中存储对脚手架 IdentityDbContext 实体的用户 ID 的引用。我还想确保我不必在每次用户打开新页面时都为自定义实体查询数据库。

我一直在查看 Whosebug,试图找到解决此问题的好建议,但我仍然不确定如何开始。所以我有几个问题:

  1. 我的方法合理吗?
  2. 如何在 UserIdentity 上找到永久 ID 号或字符串?
  3. 我是否应该将我的自定义用户实体存储在某种上下文中,这样我就不必一直查询它?如果可以,怎么做?

非常感谢所有帮助!

看起来您的要求是存储有关当前用户的自定义信息,而不是存储在有关当前用户的身份中的信息。

对于更简单的用例,您可以创建自己的用户 class 从 IdentityUser 派生并在其中添加其他属性,让 Identity 负责所有持久性和检索。

对于更复杂的用例,您可以按照您所采用的方法创建自己的表来存储用户相关信息。

看来你采用了第二种方法

Is my approach a sensible one?

我也这么认为。在 Identity 表中埋藏大量关于用户的业务特定上下文会将您与 Identity 实现紧密绑定。

How do I find a permanent id number or string to couple with on the UserIdentity?

IdentityUser user = await UserManager<IdentityUser>.FindByNameAsync(username);
string uniqueId = user.Id;

// or, if the user is signed in ...
string uniqueId = UserManager<IdentityUser>.GetUserId(HttpContext.User);

Should I store my custom user entity in some sort of context so I don't have to query it all the time? If so, how?

假设您有一个来自您自己的 DbContext 的 class 结构,用于存储有关用户的自定义信息,然后您可以在用户登录时检索该信息,将其序列化,并将其放入声明中理赔本金。然后,您可以在每次请求时使用它,而无需返回数据库。你可以根据需要从Claims集合中反序列化,按需使用。

如何...

创建一个 CustomUserClaimsPrincipalFactory(这将在用户通过从 ICustomUserInfoService 检索数据并存储在声明中进行身份验证时添加自定义声明):

public class CustomUserClaimsPrincipalFactory 
    : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
    private readonly ICustomUserInfoService _customUserInfoService;

    public CustomUserClaimsPrincipalFactory(
        UserManager<ApplicationUser> userManager,
        RoleManager<IdentityRole> roleManager,
        IOptions<IdentityOptions> optionsAccessor,
        ICustomUserInfoService customUserInfoService) 
            : base(userManager, roleManager, optionsAccessor) 
    {
        _customUserInfoService= customUserInfoService;
    }

    protected override async Task<ClaimsIdentity> GenerateClaimsAsync(
        ApplicationUser user)
    {
        var identity = await base.GenerateClaimsAsync(user);

        MyCustomUserInfo customUserInfo = 
            await _customUserInfoService.GetInfoAsync(); 


        // NOTE:
        // ... to add more claims, the claim type need to be registered
        // ... in StartUp.cs : ConfigureServices
        // e.g 
        //services.AddIdentityServer()
        //    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
        //    {
        //        options.IdentityResources["openid"].UserClaims.Add("role");
        //        options.ApiResources.Single().UserClaims.Add("role");
        //        options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
        //        options.ApiResources.Single().UserClaims.Add("my-custom-info");
        //    });
        List<Claim> claims = new List<Claim>
        {
            // Add serialized custom user info to claims
            new Claim("my-custom-info", JsonSerializer.Serialize(customUserInfo))
        };
        identity.AddClaims(claims.ToArray());

        return identity;
    }
}

在 Startup.cs 中注册您的 CustomUserInfoService(您自己的服务以从数据库中获取您的自定义用户信息):

services.AddScoped<ICustomUserInfoService>(_ => new CustomUserInfoService());

注册身份选项(使用您的 CustomUserClaimsPrincipalFactory 和 Startup.cs 中的授权。注意:添加“my-custom-info”作为已注册的用户声明类型。否则,您在 CustomUserInfoService 中的代码将无法添加声明输入“我的自定义信息”:

services.AddDefaultIdentity<IdentityUser>(options =>
    {
        options.SignIn.RequireConfirmedAccount = false;
        options.User.RequireUniqueEmail = true;
    })
    .AddRoles<IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddClaimsPrincipalFactory<CustomUserClaimsPrincipalFactory>();

services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
    {
        options.IdentityResources["openid"].UserClaims.Add("role");
        options.ApiResources.Single().UserClaims.Add("role");
        options.IdentityResources["openid"].UserClaims.Add("my-custom-info");
        options.ApiResources.Single().UserClaims.Add("my-custom-info");
    });

然后您可以从声明中检索您的自定义用户信息,而无需返回数据库,方法是:

MyCustomUserInfo customUserInfo =
    JsonSerializer.Deserialize<MyCustomUserInfo>( 
        HttpContext.User.Claims
            .SingleOrDefault(c => c.Type == "my-custom-info").Value);