【发布时间】:2021-03-11 23:18:08
【问题描述】:
我正在寻找一种方法来禁用用户而不是从系统中删除他们,这是为了保持相关数据的数据完整性。但似乎 ASPNET 身份仅提供删除帐户。
有一个新的锁定功能,但似乎可以控制锁定以禁用用户,但只有在尝试一定次数的错误密码后才能锁定用户。
还有其他选择吗?
【问题讨论】:
我正在寻找一种方法来禁用用户而不是从系统中删除他们,这是为了保持相关数据的数据完整性。但似乎 ASPNET 身份仅提供删除帐户。
有一个新的锁定功能,但似乎可以控制锁定以禁用用户,但只有在尝试一定次数的错误密码后才能锁定用户。
还有其他选择吗?
【问题讨论】:
当您创建安装了身份位的站点时,您的站点将有一个名为“IdentityModels.cs”的文件。在这个文件中有一个名为 ApplicationUser 的类,它继承自 IdentityUser。
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit https://devblogs.microsoft.com/aspnet/customizing-profile-information-in-asp-net-identity-in-vs-2013-templates/ to learn more.
public class ApplicationUser : IdentityUser
那里的cmets中有一个不错的链接,为方便点击here
本教程准确地告诉您为您的用户添加自定义属性需要做什么。
实际上,甚至不必费心看教程。
给ApplicationUser类添加一个属性,例如:
公共布尔值?已启用 { 获取;放; }
在数据库的 AspNetUsers 表中添加同名列。
轰隆隆,就是这样!
现在在您的 AccountController 中,您有一个注册操作,如下所示:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email, IsEnabled = true };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
我在创建 ApplicationUser 对象时添加了 IsEnabled = true。该值现在将保留在 AspNetUsers 表的新列中。
然后,您需要通过覆盖 ApplicationSignInManager 中的 PasswordSignInAsync 来处理检查此值作为登录过程的一部分。
我是这样做的:
public override Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool rememberMe, bool shouldLockout)
{
var user = UserManager.FindByEmailAsync(userName).Result;
if ((user.IsEnabled.HasValue && !user.IsEnabled.Value) || !user.IsEnabled.HasValue)
{
return Task.FromResult<SignInStatus>(SignInStatus.LockedOut);
}
return base.PasswordSignInAsync(userName, password, rememberMe, shouldLockout);
}
您的里程可能会有所不同,您可能不想返回该 SignInStatus,但您明白了。
【讨论】:
if (user!= null && !user.IsEnabled) { return Task.FromResult<SignInStatus>(SignInStatus.LockedOut); }。我不检查 HasValue 的原因是因为我将 IsEnabled 设为必填字段
PasswordSignInAsync。如果我们不这样做并且PasswordSignInAsync 返回Success。然后请求通过身份验证并且系统可以中断。因为可以写一些逻辑Request.IsAuthenticated。非常感谢:)
User 的默认 LockoutEnabled 属性不是指示用户当前是否被锁定的属性。这是一个属性,指示一旦AccessFailedCount 达到MaxFailedAccessAttemptsBeforeLockout 值,用户是否应该被锁定。即使用户被锁定,它也只是在LockedoutEnddateUtc 属性的持续时间内禁止用户的临时措施。因此,要永久禁用或暂停用户帐户,您可能需要引入自己的标志属性。
【讨论】:
您无需创建自定义属性。诀窍是设置
身份用户上的LockoutEnabled 属性并将LockoutoutEndDateUtc 设置为您的代码中的未来日期以锁定用户。然后,调用 UserManager.IsLockedOutAsync(user.Id) 将返回 false。
LockoutEnabled 和 LockoutoutEndDateUtc 都必须满足真实和未来日期的条件才能锁定用户。例如,如果LockoutoutEndDateUtc 的值为2014-01-01 00:00:00.000 并且LockoutEnabled 是true,则调用UserManager.IsLockedOutAsync(user.Id) 仍将返回true。我可以理解为什么微软这样设计它,这样你就可以设置一个用户被锁定的时间跨度。
但是,我认为如果LockoutEnabled 是true,那么如果LockoutoutEndDateUtc 为NULL 或未来日期,用户应该被锁定。这样您就不必担心在代码中设置两个属性(LockoutoutEndDateUtc 默认为 NULL)。您可以将LockoutEnabled 设置为true,如果LockoutoutEndDateUtc 是NULL,则用户将被无限期锁定。
【讨论】:
您需要将自己的标志引入自定义 IdentityUser 派生类,并实施/实施您自己的有关启用/禁用的逻辑,并在禁用时阻止用户登录。
【讨论】:
我实际上所做的一切:
var lockoutEndDate = new DateTime(2999,01,01);
UserManager.SetLockoutEnabled(userId,true);
UserManager.SetLockoutEndDate(userId, lockoutEndDate);
这基本上是为了启用锁定(如果您默认情况下没有这样做,然后将锁定结束日期设置为某个遥远的值。
【讨论】:
Ozz 是正确的,但是建议您查看基类,看看您是否可以找到一个检查所有符号角度的方法 - 我认为它可能是 CanSignIn?
现在 MS 是开源的,你可以看到它们的实现:
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/src/Identity/SignInManager.cs
(网址已更改为:
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Core/src/SignInManager.cs)
public class CustomSignInManager : SignInManager<ApplicationUser>
{
public CustomSignInManager(UserManager<ApplicationUser> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<ApplicationUser> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<ApplicationUser>> logger,
IAuthenticationSchemeProvider schemes) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
}
public override async Task<bool> CanSignInAsync(ApplicationUser user)
{
if (Options.SignIn.RequireConfirmedEmail && !(await UserManager.IsEmailConfirmedAsync(user)))
{
Logger.LogWarning(0, "User {userId} cannot sign in without a confirmed email.", await UserManager.GetUserIdAsync(user));
return false;
}
if (Options.SignIn.RequireConfirmedPhoneNumber && !(await UserManager.IsPhoneNumberConfirmedAsync(user)))
{
Logger.LogWarning(1, "User {userId} cannot sign in without a confirmed phone number.", await UserManager.GetUserIdAsync(user));
return false;
}
if (UserManager.FindByIdAsync(user.Id).Result.IsEnabled == false)
{
Logger.LogWarning(1, "User {userId} cannot sign because it's currently disabled", await UserManager.GetUserIdAsync(user));
return false;
}
return true;
}
}
还可以考虑覆盖 PreSignInCheck,它也调用 CanSignIn:
protected virtual async Task<SignInResult> PreSignInCheck(TUser user)
{
if (!await CanSignInAsync(user))
{
return SignInResult.NotAllowed;
}
if (await IsLockedOut(user))
{
return await LockedOut(user);
}
return null;
}
【讨论】:
options.SignIn.RequireConfirmedAccount & IUserConfirmation<TUser>,所以你不需要再更换SignInManager了。
您可以使用这些类... ASP.NET Identity 的干净实现... 这是我自己的代码。 int 在这里是主键,如果你想要不同的主键类型,你可以改变它。
IdentityConfig.cs
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(new ApplicationUserStore(context.Get<ApplicationContext>()));
manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
manager.PasswordValidator = new PasswordValidator
{
RequiredLength = 6,
RequireNonLetterOrDigit = true,
RequireDigit = true,
RequireLowercase = true,
RequireUppercase = true,
};
manager.UserLockoutEnabledByDefault = false;
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, int>(
dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) :
base(userManager, authenticationManager) { }
public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
{
return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
{
public ApplicationRoleManager(IRoleStore<ApplicationRole, int> store)
: base(store)
{
}
}
public class ApplicationRoleStore : RoleStore<ApplicationRole, int, ApplicationUserRole>
{
public ApplicationRoleStore(ApplicationContext db)
: base(db)
{
}
}
public class ApplicationUserStore : UserStore<ApplicationUser, ApplicationRole, int,
ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
public ApplicationUserStore(ApplicationContext db)
: base(db)
{
}
}
IdentityModel.cs
public class ApplicationUser : IdentityUser<int, ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
//your property
//flag for users state (active, deactive or enabled, disabled)
//set it false to disable users
public bool IsActive { get; set; }
public ApplicationUser()
{
}
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
{
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
return userIdentity;
}
}
public class ApplicationUserRole : IdentityUserRole<int>
{
}
public class ApplicationLogin : IdentityUserLogin<int>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationClaim : IdentityUserClaim<int>
{
public virtual ApplicationUser User { get; set; }
}
public class ApplicationRole : IdentityRole<int, ApplicationUserRole>
{
public ApplicationRole()
{
}
}
public class ApplicationContext : IdentityDbContext<ApplicationUser, ApplicationRole, int, ApplicationLogin, ApplicationUserRole, ApplicationClaim>
{
//web config connectionStringName DefaultConnection change it if required
public ApplicationContext()
: base("DefaultConnection")
{
Database.SetInitializer<ApplicationContext>(new CreateDatabaseIfNotExists<ApplicationContext>());
}
public static ApplicationContext Create()
{
return new ApplicationContext();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
}
}
【讨论】:
我赞成 Watson,因为 SignInManager 中有另一个公共方法接受 TUser 用户而不是字符串 userName。接受的答案仅建议使用用户名签名覆盖该方法。两者都应该被覆盖,否则有一种方法可以让禁用的用户登录。以下是基本实现中的两个方法:
public virtual async Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool lockoutOnFailure)
{
var user = await UserManager.FindByNameAsync(userName);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
}
public virtual async Task<SignInResult> PasswordSignInAsync(User user, string password, bool isPersistent, bool lockoutOnFailure)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var attempt = await CheckPasswordSignInAsync(user, password, lockoutOnFailure);
return attempt.Succeeded
? await SignInOrTwoFactorAsync(user, isPersistent)
: attempt;
}
覆盖 CanSignIn 对我来说似乎是一个更好的解决方案,因为它由 PreSignInCheck 调用,在 CheckPasswordSignInAsync 中调用。据我所知,覆盖 CanSignIn 应该涵盖所有场景。这是一个可以使用的简单实现:
public override async Task<bool> CanSignInAsync(User user)
{
var canSignIn = user.IsEnabled;
if (canSignIn) {
canSignIn = await base.CanSignInAsync(user);
}
return canSignIn;
}
【讨论】:
在 asp.net Core Identity v3 中,添加了一种防止用户登录的新方法。以前您可以要求帐户具有确认的电子邮件地址或电话号码,现在您可以指定.RequireConfirmedAccount。 IUserConfirmation<> 服务的默认实现与要求确认电子邮件地址的行为相同,提供您自己的服务来定义确认的含义。
public class User : IdentityUser<string>{
public bool IsEnabled { get; set; }
}
public class UserConfirmation : IUserConfirmation<User>
{
public Task<bool> IsConfirmedAsync(UserManager<User> manager, User user) =>
Task.FromResult(user.IsEnabled);
}
services.AddScoped<IUserConfirmation<User>, UserConfirmation>();
services.AddIdentity<User, IdentityRole>(options => {
options.SignIn.RequireConfirmedAccount = true;
} );
【讨论】: