如何重载UserManager.AddToRoleAsync(字符串userId,字符串角色)

时间:2015-03-16 07:42:58

标签: asp.net asp.net-identity

我正在使用Asp.net Identity Framework 2.1。我实现了自定义的ApplicatoinUser,ApplicationRole,ApplicationUserRole,因为我想添加对多租户的支持,即每个用户属于不同的公司,但我在所有这些公司中有3个角色,他们是用户,管理员和审批者。

我的ApplicationUserRole派生自IdentityUserRole,还有一个属性:CompanyId。此属性将指示用户在此特定公司中的角色。这些自定义类的代码附在底部。

我的问题是当我尝试覆盖ApplicationUserManager(是的,它也来自UserManager)的AddToRoleAsyncIsInRoleAsync时,我不知道如何处理新的CompanyId,看起来像现有的功能没有收到这些companyId(或tenantId)。

然后,当我尝试使用companyId包含这些函数时,我无法在ApplicatoinUserManager或其基类中找到db上下文。

我是否正在将tenantId / companyId添加到应用程序角色?

我引用了这个答案:SO linkes和此博客。ASP.NET Web Api and Identity 2.0 - Customizing Identity Models and Implementing Role-Based Authorization

我的IdentityModels:

public class ApplicationUserLogin : IdentityUserLogin<string> { }
public class ApplicationUserClaim : IdentityUserClaim<string> 
{
}
public class ApplicationUserRole : IdentityUserRole<string> 
{
    public string CompanyId { get; set; }
}

// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser<string, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>//, IAppUser
{
    public ApplicationUser()
    {
        this.Id = Guid.NewGuid().ToString();
    }
    public virtual string CompanyId { get; set; }
    public virtual List<CompanyEntity> Company { get; set; }
    public DateTime CreatedOn { get; set; }

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(ApplicationUserManager manager, string authenticationType)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, authenticationType);
        // Add custom user claims here
        return userIdentity;
    }
}

// Must be expressed in terms of our custom UserRole:
public class ApplicationRole : IdentityRole<string, ApplicationUserRole>
{
    public ApplicationRole() {}
    public ApplicationRole(string name) : this()
    {
        this.Name = name;
    }

    // Add any custom Role properties/code here
    public string Description { get; set; }
}

// Most likely won't need to customize these either, but they were needed because we implemented
// custom versions of all the other types:
public class ApplicationUserStore: UserStore<ApplicationUser, ApplicationRole, string,ApplicationUserLogin, ApplicationUserRole,ApplicationUserClaim>, IUserStore<ApplicationUser, string>, IDisposable
{
    public ApplicationUserStore()
        : this(new IdentityDbContext())
    {
        base.DisposeContext = true;
    }

    public ApplicationUserStore(DbContext context)
        : base(context)
    {
    }
}

public class ApplicationRoleStore
: RoleStore<ApplicationRole, string, ApplicationUserRole>,
IQueryableRoleStore<ApplicationRole, string>,
IRoleStore<ApplicationRole, string>, IDisposable
{
    public ApplicationRoleStore()
        : base(new IdentityDbContext())
    {
        base.DisposeContext = true;
    }

    public ApplicationRoleStore(DbContext context)
        : base(context)
    {
    }
}

My IdentityConfig:

public class ApplicationUserManager
    : UserManager<ApplicationUser, string>
{
    public ApplicationUserManager(IUserStore<ApplicationUser, string> store)
        : base(store) { }

    public static ApplicationUserManager Create(
        IdentityFactoryOptions<ApplicationUserManager> options,
        IOwinContext context)
    {
        var manager = new ApplicationUserManager(
            new UserStore<ApplicationUser, ApplicationRole, string,
                ApplicationUserLogin, ApplicationUserRole,
                ApplicationUserClaim>(context.Get<ApplicationDbContext>()));

        // Configure validation logic for usernames
        manager.UserValidator = new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = false
        };

        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            //RequireNonLetterOrDigit = true,
            //RequireDigit = true,
            //RequireLowercase = true,
            //RequireUppercase = true,
        };
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser>(
                    dataProtectionProvider.Create("ASP.NET Identity"));
        }

        // add sms and email service provider
        manager.SmsService = new EMaySmsServiceProvider();
        manager.EmailService = new ConcordyaEmailServiceProvider();

        return manager;
    }
    public string GetCurrentCompanyId(string userName)
    {
        var user = this.FindByName(userName);
        if (user == null)
            return string.Empty;

        var currentCompany = string.Empty;
        if (user.Claims.Count > 0)
        {
            currentCompany = user.Claims.Where(c => c.ClaimType == ConcordyaPayee.Core.Common.ConcordyaClaimTypes.CurrentCompanyId).FirstOrDefault().ClaimValue;
        }
        else
        {
            currentCompany = user.CurrentCompanyId;
        }
        return currentCompany;
    }

    public override Task<IdentityResult> AddToRoleAsync(string userId, string role, string companyId)
    { 
        return base.AddToRoleAsync(userId, role);
    }

    #region overrides for unit tests
    public override Task<bool> CheckPasswordAsync(ApplicationUser user, string password)
    {
        return base.CheckPasswordAsync(user, password);
    }

    public override Task<ApplicationUser> FindByNameAsync(string userName)
    {
        return base.FindByNameAsync(userName);
    }
    #endregion
}

public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
    public ApplicationRoleManager(IRoleStore<ApplicationRole, string> roleStore)
        : base(roleStore)
    {

    }

    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options,
        IOwinContext context)
    {
        return new ApplicationRoleManager(
            new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
    }
}

1 个答案:

答案 0 :(得分:4)

首先,我要感谢你们这么做。它为我的多租户角色解决方案提供了一个良好的开端。我不确定我是否100%正确,但这对我有用。

首先,您不能覆盖任何“RoleAsync”方法,但可以重载它们。其次,UserStore有一个名为“Context”的属性,可以设置为DbContext。

我不得不在UserStore和UserManager扩展类中重载“RoleAsyc”方法。以下是各种示例,以帮助您:

<强> MyUserStore

    public class MyUserStore : UserStore<MyUser, MyRole, String, IdentityUserLogin, MyUserRole, IdentityUserClaim> {

        public MyUserStore(MyDbContext dbContext) : base(dbContext) { }

        public Task AddToRoleAsync(MyUser user, MyCompany company, String roleName) {
            MyRole role = null;

            try
            {
                role = Context.Set<MyRole>().Where(mr => mr.Name == roleName).Single();
            }
            catch (Exception ex)
            {
                throw ex;
            }

            Context.Set<MyUserRole>().Add(new MyUserRole {
                Company = company,
                RoleId = role.Id,
                UserId = user.Id
            });

            return Context.SaveChangesAsync();
        }
    }

<强> MyUserManager

    public class MyUserManager : UserManager<MyUser, String>
    {
        private MyUserStore _store = null;

        public MyUserManager(MyUserStore store) : base(store)
        {
            _store = store;
        }

        public Task<IList<String>> GetRolesAsync(String userId, int companyId)
        {
            MyUser user = _store.Context.Set<MyUser>().Find(new object[] { userId });
            MyCompany company = _store.Context.Set<MyCompany>().Find(new object[] { companyId });

            if (null == user)
            {
                throw new Exception("User not found");
            }

            if (null == company)
            {
                throw new Exception("Company not found");
            }

            return _store.GetRolesAsync(user, company);
        }
    }

从这里发生了一些可怕的事情,我不知道更好的方法来管理它们。

  1. HttpContext中的用户“IsInRole”方法可以使用,但它不会对租户敏感,因此您无法再使用它。
  2. 如果您使用“授权”属性,则“可怕的事物1”的相同想法适用,但在这里您可以扩展它并使您的系统满意。示例如下:
  3. <强> MyAuthorizeAttribute

        public class MyAuthorizeAttribute : AuthorizeAttribute {
            protected override bool AuthorizeCore(HttpContextBase httpContext)
            {
                if (null == httpContext)
                {
                    throw new ArgumentNullException("httpContext");
                }
    
                HttpSessionStateBase session = httpContext.Session;
                IList<String> authorizedRoleNames = Roles.Split(',').Select(r => r.Trim()).ToList();
    
                if (!httpContext.User.Identity.IsAuthenticated)
                {
                    return false;
                }
    
                if (null == session["MyAuthorize.CachedUsername"])
                {
                    session["MyAuthorize.CachedUsername"] = String.Empty;
                }
    
                if (null == session["MyAuthorize.CachedCompanyId"])
                {
                    session["MyAuthorize.CachedCompanyId"] = -1;
                }
    
                if (null == session["MyAuthorize.CachedUserCompanyRoleNames"])
                {
                    session["MyAuthorize.CachedUserCompanyRoleNames"] = new List<String>();
                }
    
                String cachedUsername = session["MyAuthorize.CachedUsername"].ToString();
                int cachedCompanyId = (int)session["MyAuthorize.CachedCompanyId"];
                IList<String> cachedUserAllRoleNames = (IList<String>)session["MyAuthorize.CachedUserAllRoleNames"];
    
                IPrincipal currentUser = httpContext.User;
                String currentUserName = currentUser.Identity.Name;
                int currentCompanyId = (int)session["CurrentCompanyId"];//Get this your own way! I used the Session in the HttpContext.
    
                using (MyDbContext db = MyDbContext.Create())
                {
                    try
                    {
                        MyUser mUser = null;
                        ICollection<String> tmpRoleIds = new List<String>();
    
                        if (cachedUsername != currentUserName)
                        {
                            session["MyAuthorize.CachedUsername"] = cachedUsername = String.Empty;
    
                            //Reload everything
                            mUser = db.Users.Where(u => u.Username == currentUserName).Single();
    
                            session["MyAuthorize.CachedUsername"] = currentUserName;
                            session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = -1; //Force Company Reload
                            cachedUserCompanyRoleNames.Clear();
                        }
    
                        if (cachedUserCompanyRoleNames.Count != db.Users.Where(u => u.Username == currentUserName).Single().Roles.Select(r => r.RoleId).ToList().Count)
                        {
                            cachedUserCompanyRoleNames.Clear();
    
                            if (0 < currentCompanyId)
                            {
                                if(null == mUser)
                                {
                                    mUser = db.Users.Where(u => u.Username == cachedUsername).Single();
                                }
    
                                tmpRoleIds = mUser.Roles.Where(r => r.Company.Id == currentCompanyId).Select(r => r.RoleId).ToList();
    
                                session["MyAuthorize.CachedUserCompanyRoleNames"] = cachedUserCompanyRoleNames = db.Roles.Where(r => tmpRoleIds.Contains(r.Id)).Select(r => r.Name).ToList();
    
                                session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = currentCompanyId;
                            }
                        }
    
                        if (cachedCompanyId != currentCompanyId)
                        {
                            cachedUserCompanyRoleNames.Clear();
    
                            //Reload company roles
                            if (0 < currentCompanyId)
                            {
                                if(null == mUser)
                                {
                                    mUser = db.Users.Where(u => u.Username == cachedUsername).Single();
                                }
    
                                tmpRoleIds = mUser.Roles.Where(r => r.Company.Id == currentCompanyId).Select(r => r.RoleId).ToList();
    
                                session["MyAuthorize.CachedUserCompanyRoleNames"] = cachedUserCompanyRoleNames = db.Roles.Where(r => tmpRoleIds.Contains(r.Id)).Select(r => r.Name).ToList();
    
                                session["MyAuthorize.CachedCompanyId"] = cachedCompanyId = currentCompanyId;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        return false;
                    }
                }
    
                if (0 >= authorizedRoleNames.Count)
                {
                    return true;
                }
                else
                {
                    return cachedUserCompanyRoleNames.Intersect(authorizedRoleNames).Any();
                }
            }
        }
    

    最后,正如我所说,我不确定这是否是最佳方式,但它对我有用。现在,在整个系统中,确保在处理角色时使用了重载方法。我也在考虑在我编写的MVC BaseController中缓存角色,这样我就可以在所有MVC视图中获得与User.IsInRole类似的功能。