【问题标题】:Simple approach to multi-tenancy with Identity Framework. How to?使用 Identity Framework 实现多租户的简单方法。如何?
【发布时间】:2017-09-13 18:27:41
【问题描述】:

我想在使用 Identity Framework 进行身份验证的 Web api 应用程序中支持多租户。我计划拥有一个单个站点和一个包含多个租户和多个用户的数据库。我很乐意在用户名前加上租户 ID 以用于登录目的,然后在将其返回到客户端应用程序时将其剥离。
但是,我不确定如何“拦截”用户登录。也就是说,在将用户名传递给框架进行身份验证之前,我将在哪里实现为用户名添加前缀的租户 ID。
有人可以让我知道如何处理上述问题。一个小例子和在线文档的指针将被欣赏。

谢谢。

【问题讨论】:

    标签: asp.net-web-api2 asp.net-identity asp.net-identity-2 asp.net-identity-3


    【解决方案1】:

    Here Scott Brady 有一个解决方案,你可以在那里阅读解释,我只是在这里编写代码并进行一些更新。

    解决方案

    向您的用户添加新属性以确定租户。

    public class ApplicationUser : IdentityUser { public int TenantId { get; set; } }

    然后您需要自定义您的 UserStore 以使其了解您的新用户属性。

     public class ApplicationUserStore<TUser> : UserStore<TUser> 
       where TUser : ApplicationUser {
          public ApplicationUserStore(DbContext context, int tenantId)
            : base(context) {
                  TenantId = tenantId
          }
    
          public int TenantId { get; set; }
    
    
          public override Task CreateAsync(TUser user) {
              if (user == null) {
                  throw new ArgumentNullException("user");
              }
    
              user.TenantId = this.TenantId;
              return base.CreateAsync(user);
          }
    
          public override Task<TUser> FindByEmailAsync(string email) {
              return this.GetUserAggregateAsync(u => u.Email.ToUpper() == email.ToUpper() 
                 && u.TenantId == this.TenantId);
          }
    
           public override Task<TUser> FindByNameAsync(string userName) {
               return this.GetUserAggregateAsync(u => u.UserName.ToUpper() ==                                 userName.ToUpper() 
                 && u.TenantId == this.TenantId);
           }
    
    
           public override Task<IdnUser> FindByIdAsync(long userId)
           {
               return this.GetUserAggregateAsync(u => u.Id == userId && u.TenantId == this.tenantId);
           }
    }
    

    您还需要自定义IdentityDbContext 以支持多租户。

    public class ApplicationUserDbContext<TUser> : IdentityDbContext<TUser> 
      where TUser : ApplicationUser {
        public ApplicationUserDbContext(string nameOrConnectionString)
          : base(nameOrConnectionString) {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder) {
            base.OnModelCreating(modelBuilder);
    
            var user = modelBuilder.Entity<TUser>();
    
            user.Property(u => u.UserName)
                .IsRequired()
                .HasMaxLength(256)
                .HasColumnAnnotation("Index", new IndexAnnotation(
                    new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 1}));
    
            user.Property(u => u.TenantId)
                .IsRequired()
                .HasColumnAnnotation("Index", new IndexAnnotation(
                    new IndexAttribute("UserNameIndex") { IsUnique = true, Order = 2 }));
        }
    
    
       protected override DbEntityValidationResult ValidateEntity(
        DbEntityEntry entityEntry, IDictionary<object, object> items) {
          if (entityEntry != null && entityEntry.State == EntityState.Added) {
            var errors = new List<DbValidationError>();
            var user = entityEntry.Entity as TUser;
    
            if (user != null) {
                if (this.Users.Any(u => string.Equals(u.UserName, user.UserName) 
                  && u.TenantId == user.TenantId)) {
                    errors.Add(new DbValidationError("User", 
                      string.Format("Username {0} is already taken for AppId {1}", 
                        user.UserName, user.TenantId)));
                }
    
                if (this.RequireUniqueEmail 
                  && this.Users.Any(u => string.Equals(u.Email, user.Email) 
                  && u.TenantId == user.TenantId)) {
                    errors.Add(new DbValidationError("User", 
                      string.Format("Email Address {0} is already taken for AppId {1}", 
                        user.UserName, user.TenantId)));
                }
            }
            else {
                var role = entityEntry.Entity as IdentityRole;
    
                if (role != null && this.Roles.Any(r => string.Equals(r.Name, role.Name))) {
                    errors.Add(new DbValidationError("Role", 
                      string.Format("Role {0} already exists", role.Name)));
                }
            }
            if (errors.Any()) {
                return new DbEntityValidationResult(entityEntry, errors);
            }
        }
    
        return new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
        }
    }
    

    然后您可以像这样为每个租户创建单独的用户管理器:

    用户 1

    public class AppCustomerManager : UserManager<ApplicationUser>
        {
            public AppCustomerManager(IUserStore<ApplicationUser> store)
                : base(store)
            {
                UserTokenProvider = new PhoneNumberTokenProvider<ApplicationUser >();
            }
    
            public static AppCustomerManager Create(IdentityFactoryOptions<AppCustomerManager> options, IOwinContext context)
            {
                var appDbContext = context.Get<ApplicationUserDbContext>();
    
                //here you define your tenant by passing an int to userstore
                var appUserManager = new AppCustomerManager(new ApplicationUserStore(appDbContext, 1));
    
                var dataProtectionProvider = options.DataProtectionProvider;
                if (dataProtectionProvider != null)
                {
                    appUserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"))
                    {
                        TokenLifespan = TimeSpan.FromHours(6)
                    };
    
                }
    
    
                return appUserManager;
            }
        }
    

    用户2

    public class AppDeliveryManager : UserManager<ApplicationUser>
        {
            public AppDeliveryManager(IUserStore<ApplicationUser> store)
                : base(store)
            {
                UserTokenProvider = new PhoneNumberTokenProvider<ApplicationUser >();
            }
    
            public static AppDeliveryManager Create(IdentityFactoryOptions<AppDeliveryManager> options, IOwinContext context)
            {
                var appDbContext = context.Get<IdnDbContext>();
                //here you define your tenant by passing an int to userstore
                var appUserManager = new AppDeliveryManager(new ApplicationUserStore(appDbContext, 2));
    
                var dataProtectionProvider = options.DataProtectionProvider;
                if (dataProtectionProvider != null)
                {
                    appUserManager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>(dataProtectionProvider.Create("ASP.NET Identity"))
                    {
                        TokenLifespan = TimeSpan.FromHours(6)
                    };
    
                }
    
                return appUserManager;
            }
        }
    

    更新:

     var appUserManager = new AppDeliveryManager(new ApplicationUserStore(appDbContext, tenantId));
    

    tenantId 可以是每个注册的租户的 ID。

    【讨论】:

    • 感谢您的样品。但是,在 SaaS 环境中,我看不到如何为每个租户动态创建单独的 UserManager - 即新租户注册时。
    • 您只需要创建 userManager 并将tenantId 动态传递给 userStore 查看我的更新
    • 感谢您的更新。但是,回到我最初的问题,我如何/在哪里“拦截”用户名和/或租户 ID?我可能在这里遗漏了一些明显的东西。当用户登录框架时,使用 IdentityFactoryOptions 和 IOwinContext 调用我的 ApplicationUserManager 的静态 Create() 方法。这是我应该设置租户 ID 的地方(例如,从用户名中提取)。但是,我不确定如何获取用户名。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-03
    • 1970-01-01
    相关资源
    最近更新 更多