|
|
环境
- http://ASP.NET Core 3.1
- EntityFramework Core 3.0
结论
使用UserManager来设置用户密码通过了以下两个步骤:
- 使用PasswordHasher来生成密码Hash;
- 将密码Hash赋值给User的PasswordHash属性;
虽然过程只有两步,但是整个源码还是有点复杂的。
// Todo : 在这里加入一个整体的调用过程图
源码分析过程
在给用户添加密码时,通常会调用UserManager<TUser>.AddPasswordAsync(TUser, String)方法。源码如下(源码地址UserManager.cs),
public virtual async Task<IdentityResult> AddPasswordAsync(TUser user, string password)
{
ThrowIfDisposed();
var passwordStore = GetPasswordStore();
if (user == null) {
throw new ArgumentNullException(nameof(user));
}
var hash = await passwordStore.GetPasswordHashAsync(user, CancellationToken);
if (hash != null) {
Logger.LogWarning(1, &#34;User {userId} already has a password.&#34;, await GetUserIdAsync(user));
return IdentityResult.Failed(ErrorDescriber.UserAlreadyHasPassword());
}
var result = await UpdatePasswordHash(passwordStore, user, password);
if (!result.Succeeded) {
return result;
}
return await UpdateUserAsync(user);
}
这段代码首先调用了passwordStore.GetPasswordHashAsync方法,来检查用户是否已经设置了密码。如果用户已经设置了密码,则抛出一个异常。如果用户没有设置密码,则调用UpdatePasswordHash方法来为用户设置密码。源码如下(源码地址UserManager.cs):
private async Task<IdentityResult> UpdatePasswordHash(
IUserPasswordStore<TUser> passwordStore,
TUser user,
string newPassword,
bool validatePassword = true)
{
if (validatePassword) {
var validate = await ValidatePasswordAsync(user, newPassword);
if (!validate.Succeeded) { return validate; }
}
var hash = newPassword != null ? PasswordHasher.HashPassword(user, newPassword) : null;
await passwordStore.SetPasswordHashAsync(user, hash, CancellationToken);
await UpdateSecurityStampInternal(user);
return IdentityResult.Success;
}
UpdatePasswordHash方法通过PasswordHasher.HashPassword方法来生成密码Hash,然后调用passwordStore.SetPasswordHashAsync方法来更新用户密码。这两个方法分别存在于PasswordHasher对象和passwordStore对象。PasswordHasher对象比较简单,这里先看一下passwordStore是怎么来的。UpdatePasswordHash方法中的passwordStore是从AddPasswordAsync传递过来的,而AddPasswordAsync中的passwordStore则是通过调用GetPasswordStore方法获得。再看一下GetPasswordStore方法(源码地址UserManager.cs):
private IUserPasswordStore<TUser> GetPasswordStore() {
var cast = Store as IUserPasswordStore<TUser>;
if (cast == null) {
throw new NotSupportedException(Resources.StoreNotIUserPasswordStore);
}
return cast;
}
原来passwordStore是由Store对象通过类型转换转换为IUserPasswordStore类型的。Store对象和刚才的PasswordHasher对象都是通过构造函数传递进来的。在http://ASP.NET Core中这些对象都是通过依赖注入加入到IOC容器中的。UserManager构造函数的部分源码如下(源码地址UserManager.cs):
protected internal IUserStore<TUser> Store { get; set; }
public IPasswordHasher<TUser> PasswordHasher { get; set; }
public UserManager(IUserStore<TUser> store,
IPasswordHasher<TUser> passwordHasher,
...)
{
if (store == null) {
throw new ArgumentNullException(nameof(store));
}
Store = store;
PasswordHasher = passwordHasher;
...
}
值得注意的是,Store对象的类型是IUserStore。然而在设置密码时,却转换成类型IUserPasswordStore。因此,需要查看一下IUserPasswordStore接口定义(源码地址IUserPasswordStore.cs):
public interface IUserPasswordStore<TUser> :
IUserStore<TUser>
where TUser : class
{
Task SetPasswordHashAsync(TUser user, string passwordHash, CancellationToken cancellationToken);
Task<string> GetPasswordHashAsync(TUser user, CancellationToken cancellationToken);
Task<bool> HasPasswordAsync(TUser user, CancellationToken cancellationToken);
}
可以看出来IUserPasswordStore继承于IUserStore,并且定义了三个方法,其中就包括前述GetPasswordHashAsync方法和SetPasswordHashAsync方法。由于是父接口转换成子接口,因此之前在GetPasswordStore方法中进行类型转换后需要判断是否转换成功。
到目前为止,我们已经弄清楚UserManager中设置密码的逻辑。但是IUserStore对象是如何创建并注册进IOC容器的,实现IUserStore接口的具体类型是什么,SetPasswordHashAsync方法具体又进行了什么操作,依然还是不清楚。在http://ASP.NET Core中增加Identity是,使用了如下代码(这里不是http://ASP.NET Core的源码):
services
.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
通过这段代码,猜测应该是在AddEntityFrameworkStores中进行了创建和注册IUserStore,进入源码查看(源码地址IdentityEntityFrameworkBuilderExtensions.cs):
public static IdentityBuilder AddEntityFrameworkStores<TContext>(this IdentityBuilder builder)
where TContext : DbContext
{
AddStores(builder.Services, builder.UserType, builder.RoleType, typeof(TContext));
return builder;
}
这段代码调用AddStores,并且传入了四个参数: 1. builder.Services:IOC容器 2. builder.UserType:用户定义的User类型,此例中为ApplicationUser 3. builder.RoleType:用户定义的Role类型,此例中为IdentityRole 4. typeof(TContext):用户定义的EFCore DbContext类型,此例中为ApplicationDbContext AddStores方法源码如下(源码地址IdentityEntityFrameworkBuilderExtensions.cs)。AddStores函数会根据是否传入roleType来进行不同的创建操作。由于本例中传入了roleType,为了清晰只查看这部分代码:
private static void AddStores(
IServiceCollection services,
Type userType,
Type roleType,
Type contextType)
{
// 调用FindGenericBaseType方法获取userType的基类型
var identityUserType = FindGenericBaseType(userType, typeof(IdentityUser<>));
if (identityUserType == null) {
throw new InvalidOperationException(Resources.NotIdentityUser);
}
// 获取IdentityUser<>的第一个范型参数,默认是string
// IdentityUser的定义如下:
// public class IdentityUser : IdentityUser<string>
var keyType = identityUserType.GenericTypeArguments[0];
...
// 调用FindGenericBaseType方法获取roleType的基类型
var identityRoleType = FindGenericBaseType(roleType, typeof(IdentityRole<>));
if (identityRoleType == null) {
throw new InvalidOperationException(Resources.NotIdentityRole);
}
Type userStoreType = null;
Type roleStoreType = null;
// 调用FindGenericBaseType方法获取contextType的基类型
var identityContext = FindGenericBaseType(contextType, typeof(IdentityDbContext<,,,,,,,>));
if (identityContext == null) {
// identityContext为空,代表contextType不是从IdentityDbContext继承来的
userStoreType = typeof(UserStore<,,,>).MakeGenericType(userType, roleType, contextType, keyType);
roleStoreType = typeof(RoleStore<,,>).MakeGenericType(roleType, contextType, keyType);
}
else {
userStoreType = typeof(UserStore<,,,,,,,,>).MakeGenericType(userType, roleType, contextType,
identityContext.GenericTypeArguments[2],
identityContext.GenericTypeArguments[3],
identityContext.GenericTypeArguments[4],
identityContext.GenericTypeArguments[5],
identityContext.GenericTypeArguments[7],
identityContext.GenericTypeArguments[6]);
roleStoreType = typeof(RoleStore<,,,,>).MakeGenericType(roleType, contextType,
identityContext.GenericTypeArguments[2],
identityContext.GenericTypeArguments[4],
identityContext.GenericTypeArguments[6]);
}
// 通过IOC容器创建IUserStore
services.TryAddScoped(typeof(IUserStore<>).MakeGenericType(userType), userStoreType);
services.TryAddScoped(typeof(IRoleStore<>).MakeGenericType(roleType), roleStoreType);
...
}
在源码中我添加了一些注释来帮助理解。从这里可以发现,AddStores方法根据用户定义的类型,通过IOC容器创建了UserStore<,,,,,,,,>范型类的对象,并以IUserStore类型注册进IOC容器。也就是说,UserManager中传入的Store就是这个UserStore<,,,,,,,,>范型类的对象。为了解决刚才提出的最后一个问题,SetPasswordHashAsync方法具体进行了什么操作,继续查看UserStore的源码(源码地址UserStore.cs):
public class UserStore<TUser, TRole, TContext, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim> :
UserStoreBase<TUser, TRole, TKey, TUserClaim, TUserRole, TUserLogin, TUserToken, TRoleClaim>,
IProtectedUserStore<TUser>
where TUser : IdentityUser<TKey>
where TRole : IdentityRole<TKey>
where TContext : DbContext
where TKey : IEquatable<TKey>
where TUserClaim : IdentityUserClaim<TKey>, new()
where TUserRole : IdentityUserRole<TKey>, new()
where TUserLogin : IdentityUserLogin<TKey>, new()
where TUserToken : IdentityUserToken<TKey>, new()
where TRoleClaim : IdentityRoleClaim<TKey>, new()
UserStore<,,,,,,,,>继承于UserStoreBase<,,,,,,,,>,并且在UserStore<,,,,,,,,>中并没有SetPasswordHashAsync方法。看来是调用了UserStoreBase<,,,,,,,,>的SetPasswordHashAsync方法。查看源码得知,8个范型参数的UserStoreBase<,,,,,,,,>中也没有SetPasswordHashAsync方法,并且它继承于5个范型参数的UserStoreBase<,,,,>类,于是又继续穿透。UserStoreBase<,,,,>类源码如下(源码地址UserStoreBase.cs):
public virtual Task SetPasswordHashAsync(
TUser user,
string passwordHash,
CancellationToken cancellationToken = default(CancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null) {
throw new ArgumentNullException(nameof(user));
}
user.PasswordHash = passwordHash;
return Task.CompletedTask;
}
看到这里终于明白了,原来设置密码就是把计算好的密码Hash赋值给user的属性PasswordHash。 |
|