此问题适用于具有外部身份提供者的core3 / react项目,其创建方式如下。
dotnet new react --auth Individual --use-local-db --output conf
并进行了修改以支持外部身份提供者。包装已添加
dotnet add package Microsoft.AspNetCore.Authentication.MicrosoftAccount
并且启动被修改
services.AddAuthentication()
.AddMicrosoftAccount(options =>
{
options.ClientId = Configuration["Authentication:Microsoft:ClientId"];
options.ClientSecret = Configuration["Authentication:Microsoft:ClientSecret"];
options.CallbackPath = "/signin-microsoft";
})
[遵循instructions provided by Microsoft之后,我通过注册为用户来测试我的作品。没有引发任何错误,但承诺的确认电子邮件从未到达。
按照说明末尾的故障排除建议,我在实现IEmailSender的SendEmailAsync方法的开始处设置了一个断点,并重复了该练习。断点未命中。
如果我通过更新数据库手动确认帐户,则>>
很明显,我的IEmailSender实现有效并且已正确注册。它与示例代码并不完全相同,因为我拥有自己的Exchange服务器,并且未使用SendGrid,但是它成功发送了一封电子邮件以重置密码,因此我可以无限制地重复执行任意次。
针对某种原因导致问题的可能性很小,这是我的实现方式
public class SmtpEmailSender : IEmailSender { public SmtpEmailSender(IOptions<SmtpOptions> options) { this.smtpOptions = options.Value; } private SmtpOptions smtpOptions { get; } public Task SendEmailAsync(string email, string subject, string htmlMessage) { var smtp = new SmtpClient(); if (!smtpOptions.ValidateCertificate) { smtp.ServerCertificateValidationCallback = (s, c, h, e) => true; } smtp.Connect(smtpOptions.Host, smtpOptions.Port, SecureSocketOptions.Auto); if (smtpOptions.Authenticate) { smtp.Authenticate(smtpOptions.Username, smtpOptions.Password); } var message = new MimeMessage() { Subject = subject, Body = new BodyBuilder() { HtmlBody = htmlMessage }.ToMessageBody() }; message.From.Add(new MailboxAddress(smtpOptions.Sender)); message.To.Add(new MailboxAddress(email)); return smtp.SendAsync(FormatOptions.Default, message).ContinueWith(antecedent => { smtp.Disconnect(true); smtp.Dispose(); }); } }
startup.cs
中的注册看起来像这样。
services.AddTransient<IEmailSender, SmtpEmailSender>(); services.Configure<SmtpOptions>(Configuration.GetSection("SmtpOptions"));
SmptOptions只是从appsettings.json中拖出的设置,并已注入到ctor中。显然,方面有效,否则密码重置电子邮件将无效。
注册没有任何问题,因为该应用程序停止生成有关需要阅读并遵循我链接的帐户确认说明的消息。
[查看问题是否是由于我的代码的某些无意副作用引起的,我创建了一个已检测到的IEmailSender
存根
public class DummyEmailSender : IEmailSender { private readonly ILogger logger; public DummyEmailSender(ILogger<DummyEmailSender> logger) { this.logger = logger; } public Task SendEmailAsync(string email, string subject, string htmlMessage) { logger.LogInformation($"SEND EMAIL\r\nemail={email} \r\nsubject={subject}\r\nhtmlMessage={htmlMessage}\r\n{new StackTrace().ToString().Substring(0,500)}"); return Task.CompletedTask; } }
我还更新了服务注册以进行匹配。
这是可能的最简单的检测存根,并且观察到的行为是相同的,在提交“忘记密码”表单时将调用它,在提交“确认注册”表单时将<。
有没有人做过可怕的事情?怎么样?https://wone.pdconsec.net/Identity/Account/ExternalLogin?returnUrl=%2Fauthentication%2Flogin&handler=Callback
看起来像这样检查页面,我们发现注册按钮将表格发送到/Identity/Account/ExternalLogin?returnUrl=%2Fauthentication%2Flogin&handler=Confirmation
此代码可从dotnet存储库中获得。克隆存储库https://github.com/dotnet/aspnetcore.git
之后,我阅读了build instructions并成功构建了dotnet 5预览。然后我运行了clean
,然后切换到带标签的分支release/3.1
以为core3.1构建调试程序包,但这失败了,因为带标签的分支启用了一个版本太旧的msbuild版本,并且错误消息建议了补救措施似乎不起作用。由于我对PowerShell的掌握不强(构建脚本为PowerShell),因此我只能进行代码检查。相关代码如下所示。
public override async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
// If account confirmation is required, we need to show the link if we don't have a real email sender
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
}
await _signInManager.SignInAsync(user, isPersistent: false);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
看来应该可以使用。我们知道什么?
CreateUser
被调用并成功。我们知道这是因为用户是在数据库中创建的。因此它肯定已经过去了,这意味着ModelState
不为空,.IsValid
为真。result.Succeeded
为true,则应该显示一条日志消息,例如“用户使用Microsoft帐户提供程序创建帐户”https://localhost:5001/Identity/Account/[email protected]
但是没有日志可确认。
really
https://localhost:5001/Identity/Account/[email protected]
太疯狂了。为了到达那里,userManager.AddLoginAsync()
必须返回true,在这种情况下的下一行是写给记录器的关于创建用户帐户的信息。这没有道理。
此问题适用于具有外部身份提供者的core3 / react项目,其创建方式如下。 dotnet new react --auth单个--use-local-db --output conf,并进行了修改以支持...
string token = await userManager.GenerateEmailConfirmationTokenAsync(user);
string urltoken = Base64UrlEncoder.Encode(token);
string link = string.Format(emailOptions.ConfirmationUrl, user.Id, urltoken);
string body = $"<a href='{link}'>confirm</a>";
await emailSender.SendEmailAsync(user.Email, "confirmation", body);