所以我有 API 和 MVC 项目。我的目标是执行以下操作:当用户从 API 项目注册时,他应该能够从 MVC 项目进行身份验证,反之亦然。几乎一切正常,但有一个问题。在 MVC Identity 中我无法进行实际登录,我的意思是我不能使用 SignInManager.SignInAsync() 方法来 SignInUser 然后使用 _LoginPartial 视图检查用户是否登录。
由于身份密码哈希和我的哈希之间的差异,我决定创建 CustomSignInManager 类来检查密码哈希是否相同等。
CustomSignInManager 类:
public class CustomSignInManager : SignInManager<User>
{
private readonly IUserAuthentication _userAuthentication;
private readonly Encryptor<User> _encryptor;
public CustomSignInManager(UserManager<User> userManager,
IHttpContextAccessor contextAccessor,
IUserClaimsPrincipalFactory<User> claimsFactory,
IOptions<IdentityOptions> optionsAccessor,
ILogger<SignInManager<User>> logger,
IAuthenticationSchemeProvider schemes,
IUserConfirmation<User> confirmation,
IUserAuthentication userAuthentication,
Encryptor<User> encryptor)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger, schemes, confirmation)
{
_userAuthentication = userAuthentication;
_encryptor = encryptor;
}
public async Task<AuthenticationResponse> PasswordSignInAsync(string email, string password)
{
var user = await UserManager.FindByEmailAsync(email);
if (user == null)
{
throw new InvalidCredentialsException(ErrorMessages.InvalidCredentialsException);
}
if (_encryptor.VerifyHashedPassword(user, user.PasswordHash, password) != PasswordVerificationResult.Success)
{
throw new InvalidCredentialsException(ErrorMessages.InvalidCredentialsException);
}
var request = new AuthenticationRequest
{
Email = email,
Password = password,
};
var response = await _userAuthentication.AuthenticateAsync(request);
if (response == null)
{
throw new InvalidCredentialsException(ErrorMessages.InvalidCredentialsException);
}
await SignInAsync(user, isPersistent: false);
return response;
}
}
Identity DI 容器注入的扩展:
public static class IdentityExtension
{
public static IServiceCollection AddIdentityUser(this IServiceCollection services)
{
services.AddDefaultIdentity<User>()
.AddEntityFrameworkStores<EventsDemoDbContext>();
services.AddScoped<SignInManager<User>, CustomSignInManager>();
services.AddScoped<CustomSignInManager>();
return services;
}
}
登录剃须刀页面:
public class LoginModel : PageModel
{
private readonly CustomSignInManager _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(CustomSignInManager signInManager, ILogger<LoginModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public IList<AuthenticationScheme> ExternalLogins { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string ErrorMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
returnUrl ??= Url.Content("~/");
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var response = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password);
if (response != null)
{
CreateAuthenticationCookie(response);
_logger.LogInformation("User logged in.");
return LocalRedirect(returnUrl);
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
public void CreateAuthenticationCookie(AuthenticationResponse response)
{
var serializedResponse = JsonConvert.SerializeObject(response);
HttpContext.Response.Cookies.Append("jwt", serializedResponse, new CookieOptions()
{
HttpOnly = true,
Secure = true,
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddMinutes(response.AccessTokenExpiresMinutes),
});
//var authCookie = Request.Cookies["jwt"];
//if (authCookie != null)
//{
// var authResponse = JsonConvert.DeserializeObject<AuthenticationResponse>(authCookie);
//}
}
}
这是_LoginPartial视图:
@using EventsDemo.Domain.Users
@using EventsDemo.Web.Services
@using Microsoft.AspNetCore.Identity
@inject CustomSignInManager SignInManager
@inject UserManager<User> UserManager
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a id="manage" class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
</li>
<li class="nav-item">
<form id="logoutForm" class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "User")">
<button id="logout" type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" id="register" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" id="login" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>
问题是这个 if 条件永远不会为真,因为在 PasswordSignInAsync() 方法中,我猜 await SignInAsync() 方法不起作用
if (response == null)
{
throw new InvalidCredentialsException(ErrorMessages.InvalidCredentialsException);
}
await SignInAsync(user, isPersistent: false);
return response;
或者在 _LoginPartial 视图中有第二个选项,我正在使用 CustomSignInManager 的不同实例,并且在 Login razor 页面不同。
我该怎么做才能让它发挥作用?除了这个,其他一切都有效。我在注入 CustomSignInManager 期间遗漏了什么吗?甚至我做得对吗?