Microsoft ASP.NET标识 – 具有相同名称的多个用户
我正在尝试一些非常奇特的东西我相信并且我遇到了一些问题,我希望可以在StackOverflow上的用户帮助下解决。
故事
我正在编写和申请,需要认证和注册。 我选择使用Microsoft.AspNet.Identity
。 我过去没有经常使用它,所以不要在这个决定上评判我。
上面提到的框架包含一个users表,它包含所有注册用户。
我已经创建了一个示例图片来展示应用程序的工作方式。
该应用程序包含3个不同的组件:
- 后端(WebAPI)。
- 客户(直接使用WebAPI)。
- 最终用户(使用移动应用程序 – iOS)。
所以,我确实有一个客户可以注册的后端。 每个成员有一个唯一用户,所以这里没问题。 客户是这里的公司,最终用户是公司的客户。
您可能已经看到了问题, User 1
很可能是Customer 1
,也是Customer 2
。
现在,客户可以邀请会员使用移动应用程序。 当客户这样做时,最终用户确实会收到一封电子邮件,其中包含自己激活的链接。
现在,只要您的用户是唯一的,这一切都正常,但我确实拥有一个Customer 1
和Customer 2
。 两个客户都可以邀请同一个用户,用户需要注册2次,每个Customer
。
问题
在Microsoft.AspNet.Identity
框架中,用户应该是唯一的,根据我的情况,我无法管理。
这个问题
是否可以向IdentityUser
添加额外的参数以确保用户是唯一的?
我已经做了什么
-
创建一个inheritance自
IdentityUser
的自定义类,其中包含一个应用程序ID:public class AppServerUser : IdentityUser { #region Properties ///
/// Gets or sets the id of the member that this user belongs to. /// public int MemberId { get; set; } #endregion } -
相应地更改了我的
IDbContext
:public class AppServerContext : IdentityDbContext, IDbContext { }
-
使用框架的已修改调用。
IUserStore -> IUserStore UserManager(_userStore) -> UserManager(_userStore);
_userStore
偏离IUserStore
类型的过程
但是,当我使用已经使用的用户名注册用户时,我仍然收到一条错误消息,指出已经使用了用户名:
var result = await userManager.CreateAsync(new AppServerUser {UserName = "testing"}, "testing");
我相信的是一个解决方案
我相信我需要更改UserManager
但我不确定。 我希望这里有人对框架有足够的了解,可以帮助我,因为它确实阻碍了我们的应用程序开发。
如果不可能,我也想知道,也许你可以指出另一个允许我这样做的框架。
注意:我不想自己编写完整的用户管理,因为这将重新启动轮子。
首先,我理解你的想法背后的想法,因此我将开始解释“为什么”你不能创建具有相同名称的多个用户。
具有相同名称的用户名:您现在遇到的问题与IdentityDbContext有关。 如您所见( https://aspnetidentity.codeplex.com/SourceControl/latest#src/Microsoft.AspNet.Identity.EntityFramework/IdentityDbContext.cs),identityDbContext设置有关唯一用户和角色的规则,首先是模型创建:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { if (modelBuilder == null) { throw new ArgumentNullException("modelBuilder"); } // Needed to ensure subclasses share the same table var user = modelBuilder.Entity() .ToTable("AspNetUsers"); user.HasMany(u => u.Roles).WithRequired().HasForeignKey(ur => ur.UserId); user.HasMany(u => u.Claims).WithRequired().HasForeignKey(uc => uc.UserId); user.HasMany(u => u.Logins).WithRequired().HasForeignKey(ul => ul.UserId); user.Property(u => u.UserName) .IsRequired() .HasMaxLength(256) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("UserNameIndex") { IsUnique = true })); // CONSIDER: u.Email is Required if set on options? user.Property(u => u.Email).HasMaxLength(256); modelBuilder.Entity() .HasKey(r => new { r.UserId, r.RoleId }) .ToTable("AspNetUserRoles"); modelBuilder.Entity() .HasKey(l => new { l.LoginProvider, l.ProviderKey, l.UserId }) .ToTable("AspNetUserLogins"); modelBuilder.Entity() .ToTable("AspNetUserClaims"); var role = modelBuilder.Entity() .ToTable("AspNetRoles"); role.Property(r => r.Name) .IsRequired() .HasMaxLength(256) .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("RoleNameIndex") { IsUnique = true })); role.HasMany(r => r.Users).WithRequired().HasForeignKey(ur => ur.RoleId); }
其次是validation实体:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary
提示:您可以轻松地克服此问题,在您当前拥有的ApplicationDbContext上,覆盖这两种方法以克服此validation
警告如果没有该validation,您现在可以使用具有相同名称的多个用户,但您必须实施规则,阻止您使用相同的用户名在同一客户中创建用户。 你可以做的是,将其添加到validation中。
希望帮助很有价值:)干杯!
可能有人可以找到这个有用的。 在我们的项目中,我们使用ASP.NET身份2,有一天我们遇到了两个用户具有相同名称的情况。 我们在我们的应用程序中使用电子邮件作为登录,它们确实必须是唯一的。 但我们不希望用户名具有唯一性。 我们所做的只是定制了几类身份框架,如下所示:
-
通过在UserName字段上创建非唯一的索引并以棘手的方式覆盖
ValidateEntity
来更改我们的AppIdentityDbContext
。 然后使用迁移更新数据库。 代码如下:public class AppIdentityDbContext : IdentityDbContext
{ public AppIdentityDbContext() : base("IdentityContext", throwIfV1Schema: false) { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // This needs to go before the other rules! *****[skipped some other code]***** // In order to support multiple user names // I replaced unique index of UserNameIndex to non-unique modelBuilder .Entity () .Property(c => c.UserName) .HasColumnAnnotation( "Index", new IndexAnnotation( new IndexAttribute("UserNameIndex") { IsUnique = false })); modelBuilder .Entity () .Property(c => c.Email) .IsRequired() .HasColumnAnnotation( "Index", new IndexAnnotation(new[] { new IndexAttribute("EmailIndex") {IsUnique = true} })); } /// /// Override 'ValidateEntity' to support multiple users with the same name /// /// /// ///protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary -
创建
IIdentityValidator
接口的自定义类,并将其设置为UserManager
属性:.UserValidator public class AppUserValidator : IIdentityValidator
{ /// /// Constructor /// /// public AppUserValidator(UserManagermanager) { Manager = manager; } private UserManager Manager { get; set; } /// /// Validates a user before saving /// /// ///public virtual async Task ValidateAsync(AppUser item) { if (item == null) { throw new ArgumentNullException("item"); } var errors = new List (); ValidateUserName(item, errors); await ValidateEmailAsync(item, errors); if (errors.Count > 0) { return IdentityResult.Failed(errors.ToArray()); } return IdentityResult.Success; } private void ValidateUserName(AppUser user, List errors) { if (string.IsNullOrWhiteSpace(user.UserName)) { errors.Add("Name cannot be null or empty."); } else if (!Regex.IsMatch(user.UserName, @"^[A-Za-z0-9@_\.]+$")) { // If any characters are not letters or digits, its an illegal user name errors.Add(string.Format("User name {0} is invalid, can only contain letters or digits.", user.UserName)); } } // make sure email is not empty, valid, and unique private async Task ValidateEmailAsync(AppUser user, List errors) { var email = user.Email; if (string.IsNullOrWhiteSpace(email)) { errors.Add(string.Format("{0} cannot be null or empty.", "Email")); return; } try { var m = new MailAddress(email); } catch (FormatException) { errors.Add(string.Format("Email '{0}' is invalid", email)); return; } var owner = await Manager.FindByEmailAsync(email); if (owner != null && !owner.Id.Equals(user.Id)) { errors.Add(string.Format(CultureInfo.CurrentCulture, "Email '{0}' is already taken.", email)); } } } public class AppUserManager : UserManager { public AppUserManager( IUserStore store, IDataProtectionProvider dataProtectionProvider, IIdentityMessageService emailService) : base(store) { // Configure validation logic for usernames UserValidator = new AppUserValidator(this); -
最后一步是改变
AppSignInManager
。 因为现在我们的用户名不是唯一的,我们使用电子邮件登录:public class AppSignInManager : SignInManager
{ .... public virtual async Task PasswordSignInViaEmailAsync(string userEmail, string password, bool isPersistent, bool shouldLockout) { var userManager = ((AppUserManager) UserManager); if (userManager == null) { return SignInStatus.Failure; } var user = await UserManager.FindByEmailAsync(userEmail); if (user == null) { return SignInStatus.Failure; } if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } if (await UserManager.CheckPasswordAsync(user, password)) { await UserManager.ResetAccessFailedCountAsync(user.Id); await SignInAsync(user, isPersistent, false); return SignInStatus.Success; } if (shouldLockout) { // If lockout is requested, increment access failed count which might lock out the user await UserManager.AccessFailedAsync(user.Id); if (await UserManager.IsLockedOutAsync(user.Id)) { return SignInStatus.LockedOut; } } return SignInStatus.Failure; } 现在代码看起来像:
[HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task
Index(User model, string returnUrl) { if (!ModelState.IsValid) { return View(model); } var result = await signInManager.PasswordSignInViaEmailAsync( model.Email, model.Password, model.StaySignedIn, true); var errorMessage = string.Empty; switch (result) { case SignInStatus.Success: if (IsLocalValidUrl(returnUrl)) { return Redirect(returnUrl); } return RedirectToAction("Index", "Home"); case SignInStatus.Failure: errorMessage = Messages.LoginController_Index_AuthorizationError; break; case SignInStatus.LockedOut: errorMessage = Messages.LoginController_Index_LockoutError; break; case SignInStatus.RequiresVerification: throw new NotImplementedException(); } ModelState.AddModelError(string.Empty, errorMessage); return View(model); }
PS我真的不喜欢我如何覆盖ValidateEntity
方法。 但我决定这样做,因为我必须实现几乎与IdentityDbContext
相同的DbContext类,因此我必须在我的项目中更新身份框架包时跟踪它的更改。