Создание безопасного веб-приложения ASP.NET MVC 5 с входом, подтверждением электронной почты и сбросом пароля (C#)
В этом руководстве показано, как создать веб-приложение ASP.NET MVC 5 с подтверждением электронной почты и сбросом пароля с помощью системы членства ASP.NET удостоверения.
Обновленная версия этого руководства, использующая .NET Core, см. в статье "Подтверждение учетной записи" и восстановление паролей в ASP.NET Core.
Создание приложения ASP.NET MVC
Начните с установки и запуска Visual Studio Express 2013 для Интернета или Visual Studio 2013. Установите Visual Studio 2013 с обновлением 3 или более поздней версии.
Примечание.
Предупреждение. Чтобы завершить работу с этим руководством, необходимо установить Visual Studio 2013 с обновлением 3 или выше.
Создайте новый веб-проект ASP.NET и выберите шаблон MVC. веб-формы также поддерживает ASP.NET Identity, чтобы выполнить аналогичные действия в приложении веб-форм.
Оставьте проверку подлинности по умолчанию как отдельные учетные записи пользователей. Если вы хотите разместить приложение в Azure, установите флажок. Далее в руководстве мы развернем его в Azure. Вы можете бесплатно открыть учетную запись Azure.
Запустите приложение, щелкните ссылку "Регистрация " и зарегистрируйте пользователя. На этом этапе единственной проверкой в сообщении является атрибут [EmailAddress] .
В обозревателе серверов перейдите к подключениям к данным\DefaultConnection\Tables\AspNetUsers, щелкните правой кнопкой мыши и выберите " Открыть определение таблицы".
На следующем рисунке показана
AspNetUsers
схема:Щелкните правой кнопкой мыши таблицу AspNetUsers и выберите " Показать данные таблицы".
На этом этапе сообщение электронной почты не было подтверждено.Щелкните строку и выберите пункт "Удалить". Вы добавите этот адрес электронной почты еще раз на следующем шаге и отправьте подтверждение электронной почты.
Подтверждение через электронное письмо
Рекомендуется подтвердить сообщение электронной почты новой регистрации пользователя, чтобы убедиться, что они не олицетворяют кого-то другого (т. е. они не зарегистрировались с электронной почтой другого пользователя). Предположим, у вас был форум для обсуждения, вы хотите запретить "bob@example.com"
регистрацию в качестве "joe@contoso.com"
. Без подтверждения "joe@contoso.com"
электронной почты может получить нежелательные сообщения из приложения. Предположим, Боб случайно зарегистрировался как "bib@example.com"
и не заметил его, он не сможет использовать восстановление пароля, так как приложение не имеет правильного сообщения электронной почты. Подтверждение электронной почты обеспечивает только ограниченную защиту от ботов и не обеспечивает защиту от определенных спаммеров, у них есть много рабочих псевдонимов электронной почты, которые они могут использовать для регистрации.
Как правило, вы хотите запретить новым пользователям публиковать любые данные на веб-сайте, прежде чем они были подтверждены электронной почтой, текстовым сообщением SMS или другим механизмом. В следующих разделах мы включите подтверждение электронной почты и измените код, чтобы предотвратить вход только зарегистрированных пользователей до тех пор, пока не будет подтверждено их сообщение электронной почты.
Подключение SendGrid
Инструкции, описанные в этом разделе, не являются текущими. Дополнительные сведения см. в статье "Настройка поставщика электронной почты SendGrid".
Хотя в этом руководстве показано, как добавлять уведомления по электронной почте с помощью SendGrid, вы можете отправлять электронную почту с помощью SMTP и других механизмов (см . дополнительные ресурсы).
В консоли диспетчера пакетов введите следующую команду.
Install-Package SendGrid
Перейдите на страницу регистрации Azure SendGrid и зарегистрируйте бесплатную учетную запись SendGrid. Настройте SendGrid, добавив следующий код в App_Start/IdentityConfig.cs:
public class EmailService : IIdentityMessageService { public async Task SendAsync(IdentityMessage message) { await configSendGridasync(message); } // Use NuGet to install SendGrid (Basic C# client lib) private async Task configSendGridasync(IdentityMessage message) { var myMessage = new SendGridMessage(); myMessage.AddTo(message.Destination); myMessage.From = new System.Net.Mail.MailAddress( "Joe@contoso.com", "Joe S."); myMessage.Subject = message.Subject; myMessage.Text = message.Body; myMessage.Html = message.Body; var credentials = new NetworkCredential( ConfigurationManager.AppSettings["mailAccount"], ConfigurationManager.AppSettings["mailPassword"] ); // Create a Web transport for sending email. var transportWeb = new Web(credentials); // Send the email. if (transportWeb != null) { await transportWeb.DeliverAsync(myMessage); } else { Trace.TraceError("Failed to create Web transport."); await Task.FromResult(0); } } }
Вам потребуется добавить следующие компоненты:
using SendGrid;
using System.Net;
using System.Configuration;
using System.Diagnostics;
Чтобы сохранить этот пример простым, мы будем хранить параметры приложения в файле web.config :
</connectionStrings>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<!-- Markup removed for clarity. -->
<add key="mailAccount" value="xyz" />
<add key="mailPassword" value="password" />
</appSettings>
<system.web>
Предупреждение
Безопасность— никогда не храните конфиденциальные данные в исходном коде. Учетная запись и учетные данные хранятся в appSetting. В Azure вы можете безопасно хранить эти значения на вкладке "Настройка" в портал Azure. Рекомендации по развертыванию паролей и других конфиденциальных данных в ASP.NET и Azure.
Включение подтверждения электронной почты в контроллере учетной записи
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id,
"Confirm your account", "Please confirm your account by clicking <a href=\""
+ callbackUrl + "\">here</a>");
return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Убедитесь, что файл Views\Account\ConfirmEmail.cshtml имеет правильный синтаксис razor. ( Символ @в первой строке может быть пропущен. )
@{
ViewBag.Title = "Confirm Email";
}
<h2>@ViewBag.Title.</h2>
<div>
<p>
Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
</p>
</div>
Запустите приложение и щелкните ссылку "Регистрация". После отправки формы регистрации вы вошли в систему.
Проверьте свою учетную запись электронной почты и щелкните ссылку, чтобы подтвердить электронную почту.
Требовать подтверждение электронной почты перед входом
В настоящее время после завершения регистрации пользователь вошел в систему. Как правило, вы хотите подтвердить их электронную почту, прежде чем входить в систему. В приведенном ниже разделе мы изменим код, чтобы новые пользователи имели подтверждение электронной почты, прежде чем они вошли (прошли проверку подлинности). HttpPost Register
Обновите метод следующими выделенными изменениями:
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Comment the following line to prevent log in until the user is confirmed.
// await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Confirm your account",
"Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
// Uncomment to debug locally
// TempData["ViewBagLink"] = callbackUrl;
ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
+ "before you can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Закомментируя SignInAsync
метод, пользователь не войдет в систему регистрацией. Строку TempData["ViewBagLink"] = callbackUrl;
можно использовать для отладки приложения и тестирования регистрации без отправки электронной почты. ViewBag.Message
используется для отображения инструкций подтверждения. Пример скачивания содержит код для проверки подтверждения электронной почты без настройки электронной почты, а также может использоваться для отладки приложения.
Views\Shared\Info.cshtml
Создайте файл и добавьте следующую разметку razor:
@{
ViewBag.Title = "Info";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>
Добавьте атрибут Authorize в Contact
метод действия контроллера Home. Вы можете щелкнуть ссылку "Контакт" , чтобы убедиться, что у анонимных пользователей нет доступа и у пользователей с проверкой подлинности есть доступ.
[Authorize]
public ActionResult Contact()
{
ViewBag.Message = "Your contact page.";
return View();
}
Кроме того, необходимо обновить HttpPost Login
метод действия:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Require the user to have a confirmed email before they can log on.
var user = await UserManager.FindByNameAsync(model.Email);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
ViewBag.errorMessage = "You must have a confirmed email to log on.";
return View("Error");
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Обновите представление Views\Shared\Error.cshtml, чтобы отобразить сообщение об ошибке:
@model System.Web.Mvc.HandleErrorInfo
@{
ViewBag.Title = "Error";
}
<h1 class="text-danger">Error.</h1>
@{
if (String.IsNullOrEmpty(ViewBag.errorMessage))
{
<h2 class="text-danger">An error occurred while processing your request.</h2>
}
else
{
<h2 class="text-danger">@ViewBag.errorMessage</h2>
}
}
Удалите все учетные записи в таблице AspNetUsers , содержащей псевдоним электронной почты, с которым вы хотите протестировать. Запустите приложение и убедитесь, что вы не сможете войти, пока не подтвердите адрес электронной почты. После подтверждения адреса электронной почты щелкните ссылку "Контакт ".
Восстановление и сброс пароля
Удалите символы комментариев из HttpPost ForgotPassword
метода действия в контроллере учетной записи:
//
// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)
{
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user.Id)))
{
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
string code = await UserManager.GeneratePasswordResetTokenAsync(user.Id);
var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking <a href=\"" + callbackUrl + "\">here</a>");
return RedirectToAction("ForgotPasswordConfirmation", "Account");
}
// If we got this far, something failed, redisplay form
return View(model);
}
Удалите символы комментариев из ForgotPassword
ActionLink в файле представления Views\Account\Login.cshtml razor:
@using MvcPWy.Models
@model LoginViewModel
@{
ViewBag.Title = "Log in";
}
<h2>@ViewBag.Title.</h2>
<div class="row">
<div class="col-md-8">
<section id="loginForm">
@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Use a local account to log in.</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="checkbox">
@Html.CheckBoxFor(m => m.RememberMe)
@Html.LabelFor(m => m.RememberMe)
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Log in" class="btn btn-default" />
</div>
</div>
<p>
@Html.ActionLink("Register as a new user", "Register")
</p>
@* Enable this once you have account confirmation enabled for password reset functionality *@
<p>
@Html.ActionLink("Forgot your password?", "ForgotPassword")
</p>
}
</section>
</div>
<div class="col-md-4">
<section id="socialLoginForm">
@Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
</section>
</div>
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
На странице входа появится ссылка для сброса пароля.
Ссылка на подтверждение повторной отправки электронной почты
После создания локальной учетной записи пользователь отправляет сообщение электронной почты по ссылке подтверждения, необходимой для входа. Если пользователь случайно удаляет сообщение электронной почты с подтверждением или сообщение электронной почты никогда не поступает, им потребуется ссылка на подтверждение, отправленная еще раз. В следующем коде показано, как включить это.
Добавьте следующий вспомогательный метод в нижней части файла Controllers\AccountController.cs :
private async Task<string> SendEmailConfirmationTokenAsync(string userID, string subject)
{
string code = await UserManager.GenerateEmailConfirmationTokenAsync(userID);
var callbackUrl = Url.Action("ConfirmEmail", "Account",
new { userId = userID, code = code }, protocol: Request.Url.Scheme);
await UserManager.SendEmailAsync(userID, subject,
"Please confirm your account by clicking <a href=\"" + callbackUrl + "\">here</a>");
return callbackUrl;
}
Обновите метод Register, чтобы использовать новый вспомогательный элемент:
//
// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
// Comment the following line to prevent log in until the user is confirmed.
// await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false);
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account");
ViewBag.Message = "Check your email and confirm your account, you must be confirmed "
+ "before you can log in.";
return View("Info");
//return RedirectToAction("Index", "Home");
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
return View(model);
}
Обновите метод входа, чтобы повторно отправить пароль, если учетная запись пользователя не была подтверждена:
//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Require the user to have a confirmed email before they can log on.
// var user = await UserManager.FindByNameAsync(model.Email);
var user = UserManager.Find(model.Email, model.Password);
if (user != null)
{
if (!await UserManager.IsEmailConfirmedAsync(user.Id))
{
string callbackUrl = await SendEmailConfirmationTokenAsync(user.Id, "Confirm your account-Resend");
// Uncomment to debug locally
// ViewBag.Link = callbackUrl;
ViewBag.errorMessage = "You must have a confirmed email to log on. "
+ "The confirmation token has been resent to your email account.";
return View("Error");
}
}
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
Объединение учетных записей для входа в социальных сетях и локальных учетных записей входа
Вы можете объединить локальные и социальные учетные записи, щелкнув ссылку электронной почты. В следующей последовательности RickAndMSFT@gmail.com сначала создается в качестве локального входа, но сначала можно создать учетную запись в качестве социального входа, а затем добавить локальное имя входа.
Щелкните ссылку "Управление ". Обратите внимание на внешние имена входа: 0 , связанные с этой учетной записью.
Щелкните ссылку на другой вход в службу и примите запросы приложения. Две учетные записи были объединены, вы сможете войти в систему с помощью любой учетной записи. Возможно, вы хотите, чтобы пользователи добавляли локальные учетные записи в случае, если их социальный вход в службу проверки подлинности не работает, или, скорее всего, они потеряли доступ к своей учетной записи социальных параметров.
На следующем изображении Tom — это социальный вход (который можно увидеть на странице внешних имен входа: 1 , показанный на странице).
Щелкнув " Выбрать пароль" , вы можете добавить локальный журнал, связанный с той же учетной записью.
Подтверждение электронной почты более подробно
Подтверждение учетной записи и восстановление паролей с помощью ASP.NET Identity см. в этом разделе с дополнительными сведениями.
Отладка приложения
Если вы не получите сообщение электронной почты, содержащее ссылку:
- Проверьте папку нежелательной почты или нежелательной почты.
- Войдите в учетную запись SendGrid и щелкните ссылку "Действие электронной почты".
Чтобы проверить ссылку проверки без электронной почты, скачайте завершенный пример. На странице будут отображаться ссылки подтверждения и коды подтверждения.
Дополнительные ресурсы
- Ссылки на рекомендуемые ресурсы ASP.NET удостоверений
- Подтверждение учетной записи и восстановление паролей с помощью удостоверения ASP.NET переходит к более подробному восстановлению паролей и подтверждению учетной записи.
- Приложение MVC 5 с Facebook, Twitter, LinkedIn и Google OAuth2 Sign-on This tutorial показывает, как писать приложение ASP.NET MVC 5 с авторизацией Facebook и Google OAuth 2. В нем также показано, как добавить дополнительные данные в базу данных удостоверений.
- Разверните безопасное приложение ASP.NET MVC с членством, OAuth и База данных SQL в Azure. В этом руководстве показано, как защитить приложение с помощью ролей, как использовать API членства для добавления пользователей и ролей и дополнительных функций безопасности.
- Создание приложения Google для OAuth 2 и подключение приложения к проекту
- Создание приложения в Facebook и подключение приложения к проекту
- Настройка SSL в проекте