我面临一个问题,即身份验证器应用程序和电子邮件验证码都被标记为“无效”,即使它们是正确的。
在添加电子邮件身份验证之前,通过身份验证器应用程序进行的 2FA 工作正常,但在添加电子邮件版本之后,它们都损坏了。即使输入了正确的代码,身份验证器和电子邮件代码都被标记为“无效”。
我尝试过的: 检查电子邮件和身份验证器应用程序的代码生成(令牌已正确生成并发送)。 在验证之前,请仔细检查输入代码是否已删除空格和破折号。
有任何调试技巧或需要研究的领域吗? 预先感谢您的帮助!
我的代码的相关部分是:
AccountController.cs:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
model.Username = model.Username?[..Math.Min(model.Username.Length, 256)] ?? string.Empty;
var user = await _userManager.Users.FirstOrDefaultAsync(t => t.UserName == model.Username && t.AccessEnabled);
// Password attempt: lockout if failure threshold reached
var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
await LogSuccessfulLogin(user);
// Check if user needs to enable 2FA
if (user.Is2FArequired)
{
return RedirectToAction(nameof(TwoStepVerification), new { userId = user.Id, rememberMe = model.RememberMe, returnUrl });
}
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(TwoStepVerification), new { userId = user.Id, rememberMe = model.RememberMe, returnUrl });
}
if (result.IsLockedOut)
{
await LogAccountLocked(user);
return RedirectToAction(nameof(Lockout));
}
// Log failed attempt with generic message
await LogFailedLoginAttempt(user.UserName);
ModelState.AddModelError("CustomError", "Login failed. Please try again.");
return View(model);
}
// GET: Two-step verification method selection
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> TwoStepVerification(string userId, bool rememberMe, string? returnUrl = null)
{
var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == userId.ToLower() && t.AccessEnabled);
if (user == null)
{
return RedirectToAction("Login");
}
var model = new TwoStepVerificationViewModel
{
Email = user.Email, // Assume user has an email
TwoFactorMethod = string.Empty,
UserId = user.Id,
RememberMe = rememberMe,
};
ViewData["ReturnUrl"] = returnUrl;
return View(model); // This view will allow the user to choose the 2FA method (email or authenticator)
}
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> TwoStepVerification(TwoStepVerificationViewModel model, string? returnUrl = null)
{
var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == model.UserId.ToLower() && t.AccessEnabled);
// Check the selected method
if (model.TwoFactorMethod == "email")
{
return RedirectToAction(nameof(LoginWith2faEmail), new { userId = model.UserId, rememberMe = model.RememberMe, returnUrl });
}
else if (model.TwoFactorMethod == "authenticator")
{
return RedirectToAction(nameof(LoginWith2fa), new { userId = model.UserId, returnUrl });
}
return View(model);
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> LoginWith2faEmail(string userId, bool rememberMe, string? returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == userId.ToLower() && t.AccessEnabled);
LoginWith2faViewModel model = new()
{
RememberMe = rememberMe,
UserId = user.Id,
};
// Generate the token for email
var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Email");
// Prepare the email body
return View(model);
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> LoginWith2faEmail(LoginWith2faViewModel model, string? returnUrl = null)
{
// Remove spaces and dashes from the code input
var verificationCode = model.TwoFactorCode?.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorSignInAsync("Email", verificationCode, model.RememberMe, model.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("User {UserId} successfully logged in with 2FA.", model.UserId);
var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == model.UserId.ToLower() && t.AccessEnabled);
return RedirectToLocal(returnUrl);
}
if (result.IsLockedOut)
{
var user = await _userManager.GetUserAsync(User);
_logger.LogWarning("User with ID {UserId} account locked after 2FA attempt.", model.UserId);
return RedirectToAction(nameof(Lockout));
}
_logger.LogWarning("Invalid authenticator code entered");
return View(model);
}
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> LoginWith2fa(string userId, bool rememberMe = false, string? returnUrl = null)
{
// Ensure the user ID is valid and access is enabled
var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == userId.ToLower() && t.AccessEnabled);
var model = new LoginWith2faViewModel
{
RememberMe = rememberMe,
UserId = user.Id
};
ViewData["ReturnUrl"] = returnUrl;
return View(model);
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, string? returnUrl = null)
{
try
{
// Load the user by ID and ensure access is enabled
var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == model.UserId.ToLower() && t.AccessEnabled);
// Remove spaces and dashes from the code input
var verificationCode = model.TwoFactorCode?.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(verificationCode, model.RememberMe, model.RememberMachine);
if (result.Succeeded)
{
_logger.LogInformation("User {UserId} successfully logged in with 2FA.", model.UserId);
return RedirectToLocal(returnUrl);
}
else if (result.IsLockedOut)
{
_logger.LogWarning("User with ID {UserId} account locked after 2FA attempt.", user.Id);
return RedirectToAction(nameof(Lockout));
}
else
{
_logger.LogWarning("Invalid authenticator code entered for user '{UserId}'.", user.Id);
ModelState.AddModelError(string.Empty, "Invalid code. Please try again.");
return View(model);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception occurred during 2FA login attempt for user {UserId}.", model.UserId);
ModelState.AddModelError(string.Empty, "An error occurred during the login process. Please try again.");
}
ModelState.AddModelError(string.Empty, "There was a problem logging in. Please try again.");
return View(model);
}
程序.cs
builder.Services.AddDbContext<IdentityContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
})
);
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityContext>()
.AddDefaultTokenProviders();
#region Identity Configuration
builder.Services.Configure<IdentityOptions>(options =>
{
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.Password.RequiredUniqueChars = 6;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
options.User.RequireUniqueEmail = true;
options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider;
});
检查您的代码后,我看不出有任何问题,但是,问题可能出在
_signInManager
的内部工作中。
我想到了一些潜在的问题。
下面的两种方法都需要来自
TwoFactorUserIdScheme
的 cookie 出现在请求标头中。
_signInManager.TwoFactorSignInAsync()
_signInManager.TwoFactorAuthenticatorSignInAsync()
您已在
Program.cs
中正确配置了身份,因此自您调用 _signInManager.PasswordSignInAsync()
方法以来,应在标头中传递此 cookie。根据 ApplicationScheme
设置 TwoFactorUserIdScheme
或 SignInResult
cookie。
我假设
_signInManager
无法从 cookie 检索 2FA 数据,无法找到要进行身份验证的用户,因此返回失败的 SignInResult
。
如上述案例所述,
_signInManager
需要 cookie 来检索 2FA 数据并对用户进行身份验证。
如果您想使用 ViewModel 中传递的数据来检索 2FA 数据并对用户进行身份验证,您必须实现自己的登录逻辑。例如:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> LoginWith2faEmail(LoginWith2faViewModel model, string? returnUrl = null)
{
// Remove spaces and dashes from the code input
var verificationCode = model.TwoFactorCode?.Replace(" ", string.Empty).Replace("-", string.Empty);
// Find the user by Id.
var user = await _userManager.FindByIdAsync(model.UserId);
if (user == null)
{
// Do something when user isn't found.
}
// Check if user is allowed to sign in.
if (!await _signInmanager.CanSignInAsync(user) && await _userManager.IsLockedOutAsync(user))
{
// Do something when user isn't allowed to sign in.
}
if (!await _userManager.VerifyTwoFactorTokenAsync(user, "Email", verificationCode))
{
// Do something when the user inputs the wrong code.
}
// Finally when the user is allowed to sign in, sign them in.
await _signInmanager.SignInAsync(user, model.RememberMe, "Your schema name");
return View(model);
}
希望这可以帮助解决您的问题!