Поделиться через


Подтверждение учетной записи и восстановление пароля с помощью ASP.NET Identity (C#)

Перед выполнением этого руководства необходимо сначала выполнить создание безопасного веб-приложения ASP.NET MVC 5 с помощью входа, подтверждения электронной почты и сброса пароля. В этом руководстве содержатся дополнительные сведения и показано, как настроить электронную почту для подтверждения локальной учетной записи и разрешить пользователям сбрасывать забытый пароль в ASP.NET Identity.

Учетная запись локального пользователя требует, чтобы пользователь создавал пароль для учетной записи, и этот пароль хранится (безопасно) в веб-приложении. ASP.NET Identity также поддерживает учетные записи социальных параметров, которые не требуют от пользователя создания пароля для приложения. Учетные записи социальных сетей используют сторонние учетные записи (например, Google, Twitter, Facebook или Microsoft) для проверки подлинности пользователей. В этой статье рассматриваются следующие вопросы:

Новые пользователи регистрируют псевдоним электронной почты, который создает локальную учетную запись.

Изображение окна регистрации учетной записи

При нажатии кнопки Зарегистрировать на их адрес электронной почты отправляется сообщение электронной почты с подтверждением, содержащее маркер проверки.

Изображение с подтверждением отправки по электронной почте

Пользователю отправляется сообщение электронной почты с маркером подтверждения для своей учетной записи.

Изображение маркера подтверждения

Если щелкнуть ссылку, вы подтвердите учетную запись.

Изображение, подтверждающее адрес электронной почты

Восстановление и сброс пароля

Локальные пользователи, которые забыли свой пароль, могут отправить маркер безопасности в свою учетную запись электронной почты, что позволит им сбросить пароль.

Изображение окна

Вскоре пользователь получит сообщение электронной почты со ссылкой, позволяющей сбросить пароль.

Изображение: сообщение электронной почты для сброса пароля
Если щелкнуть ссылку, они перейдут на страницу Сброс.

Изображение окна сброса пароля пользователя

Если нажать кнопку Сброс , пароль будет сброшен.

Изображение с подтверждением сброса пароля

Создание веб-приложения ASP.NET

Начните с установки и запуска Visual Studio 2017.

  1. Создайте веб-проект ASP.NET и выберите шаблон MVC. веб-формы также поддерживают ASP.NET Identity, поэтому вы можете выполнить аналогичные действия в приложении веб-форм.

  2. Измените проверку подлинности на Индивидуальные учетные записи пользователей.

  3. Запустите приложение, щелкните ссылку Регистрация и зарегистрируйте пользователя. На этом этапе единственная проверка сообщения электронной почты выполняется с помощью атрибута [EmailAddress] .

  4. В Обозреватель сервера перейдите в раздел Подключения к данным\DefaultConnection\Tables\AspNetUsers, щелкните правой кнопкой мыши и выберите Открыть определение таблицы.

    Схема показана на следующем рисунке AspNetUsers :

    Изображение, показывающее схему A s p Net Users

  5. Щелкните правой кнопкой мыши таблицу AspNetUsers и выберите Пункт Показать данные таблицы.

    Изображение, показывающее данные таблицы

    На данный момент сообщение электронной почты не подтверждено.

Хранилищем данных по умолчанию для ASP.NET Identity является Entity Framework, но его можно настроить для использования других хранилищ данных и добавления дополнительных полей. См. раздел Дополнительные ресурсы в конце этого руководства.

Класс запуска OWIN ( Startup.cs ) вызывается при запуске приложения и вызывает ConfigureAuth метод в App_Start\Startup.Auth.cs, который настраивает конвейер OWIN и инициализирует ASP.NET Identity. Проверьте метод ConfigureAuth. Каждый CreatePerOwinContext вызов регистрирует обратный вызов (сохраненный OwinContextв ), который будет вызываться один раз для каждого запроса для создания экземпляра указанного типа. Вы можете задать точку останова в конструкторе и Create методе каждого типа (ApplicationDbContext, ApplicationUserManager) и убедиться, что они вызываются при каждом запросе. Экземпляр ApplicationDbContext и ApplicationUserManager хранится в контексте OWIN, доступ к которому можно получить во всем приложении. ASP.NET перехватчики удостоверений в конвейер OWIN с помощью ПО промежуточного слоя для файлов cookie. Дополнительные сведения см. в разделе Управление жизненным циклом запросов для класса UserManager в ASP.NET Identity.

При изменении профиля безопасности создается новая метка безопасности и сохраняется в SecurityStamp поле таблицы AspNetUsers . Обратите внимание, что SecurityStamp поле отличается от файла cookie безопасности. Файл cookie безопасности не хранится в AspNetUsers таблице (или где-либо еще в базе данных удостоверений). Маркер cookie безопасности самозаверяется с помощью DPAPI и создается с UserId, SecurityStamp информацией о времени окончания срока действия и .

ПО промежуточного слоя для файлов cookie проверяет файл cookie при каждом запросе. Метод SecurityStampValidator в классе попадает в Startup базу данных и периодически проверяет метку безопасности, как указано в validateInterval. Это происходит каждые 30 минут (в нашем примере), если вы не измените профиль безопасности. Для минимизации обращений к базе данных выбран 30-минутный интервал. Дополнительные сведения см. в руководстве по двухфакторной проверке подлинности .

Согласно комментариям в коде, метод поддерживает проверку подлинности UseCookieAuthentication файлов cookie. Поле SecurityStamp и связанный код обеспечивают дополнительный уровень безопасности для приложения. При смене пароля вы выйдете из браузера, с которым вы вошли. Метод SecurityStampValidator.OnValidateIdentity позволяет приложению проверять маркер безопасности при входе пользователя, который используется при смене пароля или использовании внешнего имени входа. Это необходимо для того, чтобы все маркеры (файлы cookie), созданные со старым паролем, были недействительными. В примере проекта при изменении пароля пользователя для пользователя создается новый маркер, все предыдущие маркеры становятся недействительными, а SecurityStamp поле обновляется.

Система удостоверений позволяет настроить приложение таким образом, чтобы при изменении профиля безопасности пользователей (например, при изменении пароля или изменении связанного имени входа (например, из Facebook, Google, учетной записи Майкрософт и т. д.) пользователь выходит из всех экземпляров браузера. Например, на рисунке ниже показан пример приложения "Единый выход ", который позволяет пользователю выйти из всех экземпляров браузера (в данном случае IE, Firefox и Chrome), нажав одну кнопку. Кроме того, этот пример позволяет выйти только из определенного экземпляра браузера.

Изображение, показывающее окно примера приложения для единого выхода

В примере приложения "Единый выход " показано, как ASP.NET Identity позволяет повторно создать маркер безопасности. Это необходимо для того, чтобы все маркеры (файлы cookie), созданные со старым паролем, были недействительными. Эта функция обеспечивает дополнительный уровень безопасности для приложения; При изменении пароля вы будете выйдите из системы в том месте, где вы вошли в это приложение.

Файл App_Start\IdentityConfig.cs содержит классы ApplicationUserManagerи EmailServiceSmsService . Классы EmailService и SmsService реализуют IIdentityMessageService интерфейс , поэтому в каждом классе есть общие методы для настройки электронной почты и SMS. Хотя в этом руководстве показано только, как добавить уведомления по электронной почте с помощью SendGrid, вы можете отправлять сообщения с помощью SMTP и других механизмов.

Класс Startup также содержит котловую пластину для добавления входа в социальные сети (Facebook, Twitter и т. д.). Дополнительные сведения см. в руководстве по приложению MVC 5 с Facebook, Twitter, LinkedIn и Google OAuth2 Sign-on .

Изучите ApplicationUserManager класс , который содержит сведения об удостоверениях пользователей и настраивает следующие функции:

  • Требования к надежности пароля.
  • Блокировка пользователя (попытки и время).
  • Двухфакторная проверка подлинности (2FA). Я рассмотрим 2FA и SMS в другом руководстве.
  • Подключение служб электронной почты и SMS. (Я рассмотрим SMS в другом руководстве).

Класс ApplicationUserManager является производным от универсального UserManager<ApplicationUser> класса. ApplicationUser является производным от IdentityUser. IdentityUser является производным от универсального IdentityUser класса:

//     Default EntityFramework IUser implementation
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
   where TLogin : IdentityUserLogin<TKey>
   where TRole : IdentityUserRole<TKey>
   where TClaim : IdentityUserClaim<TKey>
{
   public IdentityUser()
   {
      Claims = new List<TClaim>();
      Roles = new List<TRole>();
      Logins = new List<TLogin>();
   }

   ///     User ID (Primary Key)
   public virtual TKey Id { get; set; }

   public virtual string Email { get; set; }
   public virtual bool EmailConfirmed { get; set; }

   public virtual string PasswordHash { get; set; }

   ///     A random value that should change whenever a users credentials have changed (password changed, login removed)
   public virtual string SecurityStamp { get; set; }

   public virtual string PhoneNumber { get; set; }
   public virtual bool PhoneNumberConfirmed { get; set; }

   public virtual bool TwoFactorEnabled { get; set; }

   ///     DateTime in UTC when lockout ends, any time in the past is considered not locked out.
   public virtual DateTime? LockoutEndDateUtc { get; set; }

   public virtual bool LockoutEnabled { get; set; }

   ///     Used to record failures for the purposes of lockout
   public virtual int AccessFailedCount { get; set; }
   
   ///     Navigation property for user roles
   public virtual ICollection<TRole> Roles { get; private set; }

   ///     Navigation property for user claims
   public virtual ICollection<TClaim> Claims { get; private set; }

   ///     Navigation property for user logins
   public virtual ICollection<TLogin> Logins { get; private set; }
   
   public virtual string UserName { get; set; }
}

Указанные выше свойства совпадают со свойствами в AspNetUsers таблице, показанной выше.

Универсальные аргументы в IUser позволяют создавать классы с использованием разных типов для первичного ключа. См. пример ChangePK , в котором показано, как изменить первичный ключ со строки на int или GUID.

ApplicationUser

ApplicationUser (public class ApplicationUserManager : UserManager<ApplicationUser>) определяется в Файле Models\IdentityModels.cs как:

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in 
       //   CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, 
    DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

Выделенный выше код создает ClaimsIdentity. ASP.NET Identity и OWIN Cookie Authentication основаны на утверждениях, поэтому платформа требует, чтобы приложение создавало ClaimsIdentity для пользователя. ClaimsIdentity содержит сведения обо всех утверждениях пользователя, например об имени пользователя, возрасте и ролях, к которому принадлежит пользователь. На этом этапе можно также добавить дополнительные утверждения для пользователя.

Метод OWIN AuthenticationManager.SignIn передает ClaimsIdentity и выполняет вход пользователя:

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties(){
       IsPersistent = isPersistent }, 
       await user.GenerateUserIdentityAsync(UserManager));
}

Приложение MVC 5 с Facebook, Twitter, LinkedIn и Google OAuth2 Sign-on показывает, как можно добавить дополнительные свойства в ApplicationUser класс.

Подтверждение Email

Рекомендуется подтвердить сообщение электронной почты, по которому регистрируется новый пользователь, чтобы убедиться, что он не олицетворяет другого пользователя (т. е. он не зарегистрирован с чужой электронной почтой). Предположим, у вас есть форум для обсуждения, и вы хотите запретить "bob@example.com" регистрацию в качестве "joe@contoso.com". Без подтверждения "joe@contoso.com" сообщения электронной почты может получать нежелательные сообщения электронной почты из вашего приложения. Предположим, Что Боб случайно зарегистрировался как и не заметил этого, он не сможет использовать восстановление пароля, так как "bib@example.com" приложение не имеет правильного адреса электронной почты. Email подтверждение обеспечивает только ограниченную защиту от ботов и не обеспечивает защиту от определенных спамеров, у них есть много рабочих псевдонимов электронной почты, которые они могут использовать для регистрации. В приведенном ниже примере пользователь не сможет изменить свой пароль до тех пор, пока его учетная запись не будет подтверждена (он выберет ссылку подтверждения, полученную в учетной записи электронной почты, с помощью которого он зарегистрировался). Вы можете применить этот рабочий процесс к другим сценариям, например к отправке ссылки для подтверждения и сброса пароля в новых учетных записях, созданных администратором, отправке пользователю сообщения электронной почты при изменении профиля и т. д. Как правило, вы хотите запретить новым пользователям публиковать какие-либо данные на веб-сайте, прежде чем они будут подтверждены электронной почтой, SMS-сообщением или другим механизмом.

Создание более полного примера

В этом разделе вы будете использовать NuGet для скачивания более полного примера, с которым мы будем работать.

  1. Создайте пустой веб-проект ASP.NET.

  2. В консоли диспетчера пакетов введите следующие команды:

    Install-Package SendGrid
    Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
    

    В этом руководстве мы будем использовать SendGrid для отправки электронной почты. Пакет Identity.Samples устанавливает код, с которым мы будем работать.

  3. Задайте для проекта использование SSL.

  4. Протестируйте создание локальной учетной записи, запустив приложение, выбрав ссылку Регистрация и разместив форму регистрации.

  5. Выберите демонстрационную ссылку электронной почты, которая имитирует подтверждение электронной почты.

  6. Удалите код подтверждения ссылки на демонстрационную электронную почту из примера (код ViewBag.Link в контроллере учетной записи. DisplayEmail См. методы действий и ForgotPasswordConfirmation представления razor .

Предупреждение

Если вы измените какие-либо параметры безопасности в этом примере, рабочим приложениям потребуется пройти аудит безопасности, который явно вызывает внесенные изменения.

Изучите код в App_Start\IdentityConfig.cs

В примере показано, как создать учетную запись и добавить ее в роль Администратор. Замените адрес электронной почты в примере на адрес электронной почты, который будет использоваться для учетной записи администратора. Сейчас самый простой способ создать учетную запись администратора — использовать метод программным способом Seed . Мы надеемся, что в будущем появится средство, которое позволит создавать и администрировать пользователей и роли. Пример кода позволяет создавать пользователей и роли и управлять ими, но сначала необходимо иметь учетную запись администратора для запуска ролей и страниц администраторов пользователей. В этом примере учетная запись администратора создается при заполнения базы данных.

Измените пароль и измените имя на учетную запись, в которой можно получить Уведомления по электронной почте.

Предупреждение

Безопасность— никогда не сохраняйте конфиденциальные данные в исходном коде.

Как упоминалось ранее, app.CreatePerOwinContext вызов в классе запуска добавляет обратные вызовы к методу Create содержимого базы данных приложения, классам диспетчера пользователей и диспетчера ролей. Конвейер OWIN вызывает Create метод для этих классов для каждого запроса и сохраняет контекст для каждого класса. Контроллер учетной записи предоставляет диспетчеру пользователей из контекста HTTP (который содержит контекст OWIN):

public ApplicationUserManager UserManager
{
    get
    {
        return _userManager ?? 
    HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
    }
    private set
    {
        _userManager = value;
    }
}

Когда пользователь регистрирует локальную учетную запись, HTTP Post 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)
        {
            var 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 this link: <a href=\"" 
                                               + callbackUrl + "\">link</a>");
            // ViewBag.Link = callbackUrl;   // Used only for initial demo.
            return View("DisplayEmail");
        }
        AddErrors(result);
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

В приведенном выше коде используются данные модели для создания учетной записи пользователя с помощью введенного адреса электронной почты и пароля. Если псевдоним электронной почты находится в хранилище данных, создание учетной записи завершается сбоем и форма отображается снова. Метод GenerateEmailConfirmationTokenAsync создает маркер безопасного подтверждения и сохраняет его в хранилище данных ASP.NET Identity. Метод Url.Action создает ссылку, UserId содержащую маркер подтверждения и . Затем эта ссылка будет отправляться пользователю по электронной почте, и пользователь может выбрать ссылку в своем почтовом приложении, чтобы подтвердить свою учетную запись.

Настройка подтверждения по электронной почте

Перейдите на страницу регистрации SendGrid и зарегистрируйте бесплатную учетную запись. Добавьте код, аналогичный следующему, чтобы настроить SendGrid:

public class EmailService : IIdentityMessageService
{
   public Task SendAsync(IdentityMessage message)
   {
      return configSendGridasync(message);
   }

   private 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)
      {
         return transportWeb.DeliverAsync(myMessage);
      }
      else
      {
         return Task.FromResult(0);
      }
   }
}

Примечание

Email клиенты часто принимают только текстовые сообщения (без HTML). Необходимо указать сообщение в текстовом формате и HTML. В приведенном выше примере SendGrid это делается с помощью кода и myMessage.Html , показанного myMessage.Text выше.

В следующем коде показано, как отправлять сообщения электронной почты с помощью класса MailMessage , где message.Body возвращается только ссылка.

void sendMail(Message message)
{
#region formatter
   string text = string.Format("Please click on this link to {0}: {1}", message.Subject, message.Body);
   string html = "Please confirm your account by clicking this link: <a href=\"" + message.Body + "\">link</a><br/>";

   html += HttpUtility.HtmlEncode(@"Or click on the copy the following link on the browser:" + message.Body);
#endregion

   MailMessage msg = new MailMessage();
   msg.From = new MailAddress("joe@contoso.com");
   msg.To.Add(new MailAddress(message.Destination));
   msg.Subject = message.Subject;
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(text, null, MediaTypeNames.Text.Plain));
   msg.AlternateViews.Add(AlternateView.CreateAlternateViewFromString(html, null, MediaTypeNames.Text.Html));

   SmtpClient smtpClient = new SmtpClient("smtp.gmail.com", Convert.ToInt32(587));
   System.Net.NetworkCredential credentials = new System.Net.NetworkCredential("joe@contoso.com", "XXXXXX");
   smtpClient.Credentials = credentials;
   smtpClient.EnableSsl = true;
   smtpClient.Send(msg);
}

Предупреждение

Безопасность— никогда не сохраняйте конфиденциальные данные в исходном коде. Учетная запись и учетные данные хранятся в appSetting. В Azure эти значения можно безопасно хранить на вкладке Настройка в портал Azure. Ознакомьтесь с рекомендациями по развертыванию паролей и других конфиденциальных данных в ASP.NET и Azure.

Введите учетные данные SendGrid, запустите приложение, зарегистрируйтесь с помощью псевдонима электронной почты, чтобы выбрать ссылку подтверждения в сообщении электронной почты. Сведения о том, как это сделать с помощью учетной записи электронной почты Outlook.com, см. в статье Настройка SMTP на C# для узла OUTLOOK.COM SMTP и егоASP.NET Identity 2.0: Настройка проверки учетной записи и авторизации Two-Factor .

После нажатия кнопки Зарегистрировать на его адрес электронной почты отправляется сообщение электронной почты с подтверждением, содержащее маркер проверки.

Изображение окна подтверждения отправки сообщения электронной почты

Пользователю отправляется сообщение электронной почты с маркером подтверждения для своей учетной записи.

Изображение полученного сообщения электронной почты

Анализ кода

В следующем примере кода показан метод POST ForgotPassword.

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");
        }

        var 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 here: <a href=\"" + callbackUrl + "\">link</a>");        
        return View("ForgotPasswordConfirmation");
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

Если сообщение электронной почты пользователя не подтверждено, метод завершается автоматическим сбоем. Если ошибка была опубликована для недопустимого адреса электронной почты, злоумышленники могут использовать эти сведения, чтобы найти допустимый идентификатор пользователя (псевдонимы электронной почты) для атаки.

В следующем коде ConfirmEmail показан метод в контроллере учетных записей, который вызывается, когда пользователь выбирает ссылку подтверждения в отправленном ему сообщении электронной почты:

public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
    if (userId == null || code == null)
    {
        return View("Error");
    }
    var result = await UserManager.ConfirmEmailAsync(userId, code);
    if (result.Succeeded)
    {
        return View("ConfirmEmail");
    }
    AddErrors(result);
    return View();
}

После использования маркера забытого пароля он делается недействительным. Следующее изменение кода в методе Create (в файле App_Start\IdentityConfig.cs ) устанавливает срок действия маркеров в течение 3 часов.

if (dataProtectionProvider != null)
 {
    manager.UserTokenProvider =
       new DataProtectorTokenProvider<ApplicationUser>
          (dataProtectionProvider.Create("ASP.NET Identity"))
          {                    
             TokenLifespan = TimeSpan.FromHours(3)
          };
 }

При использовании приведенного выше кода срок действия забытого пароля и маркеров подтверждения электронной почты истекает через 3 часа. Значение по умолчанию TokenLifespan — один день.

В следующем коде показан метод подтверждения по электронной почте:

// GET: /Account/ConfirmEmail
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
   if (userId == null || code == null)
   {
      return View("Error");
   }
   IdentityResult result;
   try
   {
      result = await UserManager.ConfirmEmailAsync(userId, code);
   }
   catch (InvalidOperationException ioe)
   {
      // ConfirmEmailAsync throws when the userId is not found.
      ViewBag.errorMessage = ioe.Message;
      return View("Error");
   }

   if (result.Succeeded)
   {
      return View();
   }

   // If we got this far, something failed.
   AddErrors(result);
   ViewBag.errorMessage = "ConfirmEmail failed";
   return View("Error");
}

Чтобы сделать приложение более безопасным, ASP.NET Identity поддерживает проверку подлинности Two-Factor (2FA). См . ASP.NET Identity 2.0: Настройка проверки учетной записи и Two-Factor авторизации От Джона Аттена. Хотя вы можете настроить блокировку учетной записи в случае неудачных попыток входа с паролем, такой подход делает ваше имя входа уязвимым к блокировке DOS . Мы рекомендуем использовать блокировку учетной записи только с 2FA.

Дополнительные ресурсы