在多租户应用程序的 DbContext 中注入 userid

问题描述 投票:0回答:1

我正在构建一个具有 .net 身份的多租户应用程序。我希望每个用户在登录时都能查看自己的数据。我有一个用户解析器服务,我在其中注入 httpContextAccessor 来尝试从 HttpContext 的声明主体注入用户 id。

internal sealed class AppUserResolver : IAppUserResolver
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public AppUserResolver(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }
    
    public string GetCurrentAppUserId()
    {
        return _httpContextAccessor.HttpContext!.User
            .FindFirstValue(ClaimTypes.NameIdentifier)!;
    }
}

然后我将我的服务注入到 DbContext 类中。

public class AppDbContext : IdentityDbContext<AppUser>
{
    private readonly IAppUserResolver _appUserResolver;
    public AppDbContext(DbContextOptions<AppDbContext> options, IAppUserResolver appUserResolver)
        : base(options)
    {
        _appUserResolver = appUserResolver;
    }

    public string GetUserId()
    {
        return _appUserResolver.GetCurrentAppUserId();
    }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        modelBuilder.ApplyConfiguration(new UserInfoConfiguration(GetUserId()));
        modelBuilder.ApplyConfiguration(new EmailInfoConfiguration(GetUserId()));
        modelBuilder.ApplyConfiguration(new SocialMediaInfoConfiguration(GetUserId()));
    }
}

我如何注意到,当 DI 容器注入 DbContext 时,声明不可用,因此我没有直接在 DbContext 构造函数中获取声明,而是创建了一个仅在需要时获取它们的方法。

但问题出在 OnModleCreating 方法上。此方法仅在应用启动且 EF Core 缓存配置时调用一次。但在这个方法中,我对某些实体应用了 HasQueryFilter 方法,但是它设置为空字符串,因为当用户尝试登录时,声明为空,因此 dbContext 第一次初始化时,它会使用空用户 id 进行初始化OnModelCreating 也是如此。

internal sealed class UserInfoConfiguration : IEntityTypeConfiguration<UserInfo>
{
    private readonly string _appUserId;

    public UserInfoConfiguration(string appUserId)
    {
        _appUserId = appUserId;
    }

    public void Configure(EntityTypeBuilder<UserInfo> builder)
    {
        builder.ToTable("UserInfo");
        
        builder.HasKey(x => x.Id);

        builder.HasOne<AppUser>(x => x.AppUser)
            .WithOne(x => x.UserInfo)
            .HasForeignKey<UserInfo>(x => x.AppUserId)
            .IsRequired();

        builder.HasQueryFilter(x => x.AppUserId == _appUserId);
    }
}

登录后,声明主体可用于每个用户操作,但 OnModelCreating 已运行并已缓存。因此,之后进行的查询是错误的,并且不会带来任何结果,因为它们使用简单的字符串作为用户 ID 进行过滤。关于如何解决这个问题有什么建议,或者我应该使用用户 ID 过滤所有地方吗?

我还在服务注册处注册了 HttpContextAccessor 和我的用户解析器服务

 serviceCollection.AddHttpContextAccessor();
 serviceCollection.AddScoped<IAppUserResolver, AppUserResolver>();
.net entity-framework asp.net-core dependency-injection .net-6.0
1个回答
0
投票

实现依赖于 HttpContextAccessor 的自定义服务并将其注入 DbContext 对我来说似乎没有必要。为什么不在控制器中简单地使用

User
属性呢?授权中间件将自动使用当前用户的声明填充此属性(如果使用 cookie 身份验证方案,则从 cookie 读取)。

您可以使用

User.Identity.Claims
方便地检索所有用户声明。 用于代码重用。您可以实现这样的辅助方法,您可以从控制器调用该方法:

    public static string GetCurrentUserName(ClaimsPrincipal user)
        =>
        !string.IsNullOrWhiteSpace(user?.Identity?.Name) && user.Identity.IsAuthenticated
        ? user.Identity.Name
        : "";

此示例仅返回用户名,但您可以对其进行编辑以返回您愿意检索的确切声明。

注意:对于这种方法,您需要将相关属性注册为用户声明。这样它就在

AspNetUserClaims
表中(这是默认名称,假设您没有配置该名称)。

例如,以下是如何向用户分配声明的方法:

var someUser = userMgr.FindByNameAsync("JohnWick").Result;
userMgr.AddClaimsAsync(someUser, new Claim[]{
                                        new Claim("Company", "Mercedes")
                                    }).Result;

注意:这是声明方法,但您也可以选择使用角色(这也几乎是声明,只是灵活性稍差一些,并且您使用不同的身份方法)。 如果您不想将此信息保留在 cookie 中,那么您将必须为每个请求从用户表的数据库中读取属性,这不是一个非常高效的方法,我建议不要这样做。

注意:多租户实际上只是意味着隔离系统中的用户组,因此这方面的实现取决于您。问题是关于确定用户属于哪个组。

© www.soinside.com 2019 - 2024. All rights reserved.