在上一篇博客中项目已经做到访问数据库,但是Identity的核心是身份验证,也就是用户的登入登出,承接之前的项目,继续测试Login方法,发现result是个Failed,也就是登录失败,然后在SysUserDal中发现并没有命中看起来相关的FindByIdAsync方法,就是意味着并没有走到这一步。那么就需要考虑,在中间被封装的这部分功能是如何实现的,为何会返回Failed
https://localhost:XXXXXX/Account/Login
首先观察Login中的PasswordSignInAsync方法,发现该方法被封装,但是现在core所有源码都是开源,所以可以在github中找到 PasswordSignInAsync的所在类SignInManager的源码:
https://github.com/aspnet/AspNetCore/blob/master/src/Identity/Core/src/SignInManager.cs
在上面源码中找到两个PasswordSignInAsync方法:
/// <summary>
/// Attempts to sign in the specified <paramref name="user"/> and <paramref name="password"/> combination
/// as an asynchronous operation.
/// </summary>
/// <param name="user">The user to sign in.</param>
/// <param name="password">The password to attempt to sign in with.</param>
/// <param name="isPersistent">Flag indicating whether the sign-in cookie should persist after the browser is closed.</param>
/// <param name="lockoutOnFailure">Flag indicating if the user account should be locked if the sign in fails.</param>
/// <returns>The task object representing the asynchronous operation containing the <see name="SignInResult"/>
/// for the sign-in attempt.</returns>
public virtual async Task<SignInResult> PasswordSignInAsync(TUser 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;
}
/// <summary>
/// Attempts to sign in the specified <paramref name="userName"/> and <paramref name="password"/> combination
/// as an asynchronous operation.
/// </summary>
/// <param name="userName">The user name to sign in.</param>
/// <param name="password">The password to attempt to sign in with.</param>
/// <param name="isPersistent">Flag indicating whether the sign-in cookie should persist after the browser is closed.</param>
/// <param name="lockoutOnFailure">Flag indicating if the user account should be locked if the sign in fails.</param>
/// <returns>The task object representing the asynchronous operation containing the <see name="SignInResult"/>
/// for the sign-in attempt.</returns>
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);
}
分析我们使用的重载方法,发现了定义上的区别,UserId与UserName的区别,问题在于,执行
var user = await UserManager.FindByNameAsync(userName);
时,我们传入了UserID,但是执行的时候,将UserID当做UserName去数据库检索,所以此时返回了空对象,自然会返回给Account/Login一个Failed结果,此时,如果直接将SysUserDal中的FindByNameAsync方法变更定义,Sql语句中的Where条件直接变更为UserId是最简单的解决办法。
但是这里选用第二种办法,重写SignInManager的PasswordSignInAsync方法,将调用FindByNameAsync变更为FindByIdAsync
一、创建SysUserSignInManager:
public class SysUserSignInManager : SignInManager<SysUser>
{
public SysUserSignInManager(UserManager<SysUser> userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<SysUser> claimsFactory, IOptions<IdentityOptions> optionsAccessor, ILogger<SignInManager<SysUser>> logger, IAuthenticationSchemeProvider schemes) : base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes)
{
}
public override async Task<SignInResult> PasswordSignInAsync(SysUser 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;
}
public override async Task<SignInResult> PasswordSignInAsync(string userId, string password,
bool isPersistent, bool lockoutOnFailure)
{
var user = await UserManager.FindByIdAsync(userId);
if (user == null)
{
return SignInResult.Failed;
}
return await PasswordSignInAsync(user, password, isPersistent, lockoutOnFailure);
}
private async Task<bool> IsTfaEnabled(SysUser user)
=> UserManager.SupportsUserTwoFactor &&
await UserManager.GetTwoFactorEnabledAsync(user) &&
(await UserManager.GetValidTwoFactorProvidersAsync(user)).Count > 0;
}
二、注入到StartUp:
services.AddTransient<SignInManager<SysUser>, SysUserSignInManager>();
三、此时运行程序,调用Login,发现已经断点已经正确命中的SysUserDal中的FindByIdAsync方法,且返回了数据实体和Succeeded,说明重写生效,此时可以重写SignInManager中的任意方法
四:继续测试GetUser和Logout
https://localhost:XXXXXX/Account/GetUser
https://localhost:XXXXXX/Account/Logout
Logout之后:
至此,Identity简单的登入登出已经实现,当前的项目结构为: