我正在使用Asp.net Identity Framework 2.1。我实现了自定义的ApplicatoinUser,ApplicationRole,ApplicationUserRole,因为我想添加对多租户的支持,即每个用户属于不同的公司,但我在所有这些公司中有3个角色,他们是用户,管理员和审批者。
我的ApplicationUserRole派生自IdentityUserRole,还有一个属性:CompanyId。此属性将指示用户在此特定公司中的角色。这些自定义类的代码附在底部。
我的问题是当我尝试覆盖ApplicationUserManager(是的,它也来自UserManager)的AddToRoleAsync
,IsInRoleAsync
时,我不知道如何处理新的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>()));
}
}
答案 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);
}
}
从这里发生了一些可怕的事情,我不知道更好的方法来管理它们。
<强> 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类似的功能。