Мне нужно реализовать Identity Core с первичным ключом типа int
и пользовательской схемой User
. Кроме того, мне нужно реализовать несколько пользовательских полей в IdentityUser и Role. После того, как я все сделал и попытался использовать
await _signInManager.PasswordSignInAsync(user, vm.Password, isPersistent: false, false);
, я получаю следующее исключение, которое я не могу отладить из-за любви к нему:
System.ArgumentNullException: Value cannot be null.
Parameter name: value
at System.Security.Claims.Claim..ctor(String type, String value, String valueType, String issuer, String originalIssuer, ClaimsIdentity subject, String propertyKey, String propertyValue)
at System.Security.Claims.Claim..ctor(String type, String value)
at Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory`1.GenerateClaimsAsync(TUser user)
at Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory`2.GenerateClaimsAsync(TUser user)
at Microsoft.AspNetCore.Identity.UserClaimsPrincipalFactory`1.CreateAsync(TUser user)
at Microsoft.AspNetCore.Identity.SignInManager`1.CreateUserPrincipalAsync(TUser user)
at Microsoft.AspNetCore.Identity.SignInManager`1.SignInAsync(TUser user, AuthenticationProperties authenticationProperties, String authenticationMethod)
at Microsoft.AspNetCore.Identity.SignInManager`1.SignInOrTwoFactorAsync(TUser user, Boolean isPersistent, String loginProvider, Boolean bypassTwoFactor)
at Microsoft.AspNetCore.Identity.SignInManager`1.PasswordSignInAsync(TUser user, String password, Boolean isPersistent, Boolean lockoutOnFailure)
Да, часть значенияконструктора Claim
является нулевым. Мне нужна помощь в выяснении, почему это происходит, поскольку я не могу войти в эту часть кода.
Давайте начнем с Конфигурации идентификации:
services.AddIdentity<SpotlightUser, SpotlightRole>()
.AddEntityFrameworkStores<SpotlightContext>()
.AddDefaultTokenProviders();
Контекст:
public class SpotlightContext : IdentityDbContext<
SpotlightUser, SpotlightRole, int,
SpotlightUserClaim, SpotlightUserRole, SpotlightUserLogin,
SpotlightRoleClaim, SpotlightUserToken>
{
public SpotlightContext(DbContextOptions<SpotlightContext> options)
:base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
//inject custom model creation options here
builder.ApplyConfiguration(new SpotlightUserConfiguration());
builder.ApplyConfiguration(new SpotlightRoleConfiguration());
/*...omitted*/
}
public override int SaveChanges()
{
ApplyShadowProperties();
return base.SaveChanges();
}
/*...omitted*/
}
Вот реализации пользователя и роли:
public class SpotlightUser : IdentityUser<int>, ITrackable
{
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
public virtual ICollection<SpotlightUserClaim> Claims { get; set; }
public virtual ICollection<SpotlightUserLogin> Logins { get; set; }
public virtual ICollection<SpotlightUserToken> Tokens { get; set; }
public virtual ICollection<SpotlightUserRole> UserRoles { get; set; }
}
public class SpotlightRole : IdentityRole<int>, ITrackable
{
public DateTime CreatedDate { get; set; }
public DateTime ModifiedDate { get; set; }
public virtual ICollection<SpotlightUserRole> UserRoles { get; set; }
public virtual ICollection<SpotlightRoleClaim> RoleClaims { get; set; }
}
ITrackable
только определяет, что поля Created и Modified должны присутствовать.
Точно так же я определил всеклассы спутников:
public class SpotlightRoleClaim : IdentityRoleClaim<int>
{
public virtual SpotlightRole Role { get; set; }
}
Наконец, вот конфигурация для SpotlightUser:
public class SpotlightUserConfiguration : IEntityTypeConfiguration<SpotlightUser>
{
public void Configure(EntityTypeBuilder<SpotlightUser> builder)
{
builder.ToTable("AspNetUsers", schema: "User");
builder.Property(x => x.Company).HasMaxLength(200);
builder.Property(x => x.IsActive).HasDefaultValue(true);
builder.Property(x => x.IsAdmin).HasDefaultValue(false);
builder.HasMany(x => x.Claims)
.WithOne()
.HasForeignKey(uc => uc.UserId)
.IsRequired();
builder.HasMany(x => x.Logins)
.WithOne()
.HasForeignKey(l => l.UserId)
.IsRequired();
builder.HasMany(e => e.Tokens)
.WithOne()
.HasForeignKey(ut => ut.UserId)
.IsRequired();
builder.HasMany(e => e.UserRoles)
.WithOne(e => e.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
}
}
Как вы можете видеть эти пузырьки до GenerateClaimsAsync
, и я подозреваю, что они разбиваются вэти строки (https://github.com/aspnet/Identity/blob/master/src/Core/UserClaimsPrincipalFactory.cs)
id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName));
if (UserManager.SupportsUserSecurityStamp)
{
id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
await UserManager.GetSecurityStampAsync(user)));
}
if (UserManager.SupportsUserClaim)
{
id.AddClaims(await UserManager.GetClaimsAsync(user));
}
Почему это происходит и какую часть я упустил из виду в своей реализации? Я следовал этой статье для руководства: https://docs.microsoft.com/en-us/aspnet/core/security/authentication/customize-identity-model?view=aspnetcore-2.2#customize-the-model