Tworzenie bezpiecznej aplikacji internetowej ASP.NET MVC 5 z logowaniem, potwierdzeniem adresu e-mail i resetowaniem hasła (C#)

Autor: Rick Anderson

W tym samouczku pokazano, jak utworzyć aplikację internetową ASP.NET MVC 5 z potwierdzeniem wiadomości e-mail i resetowaniem hasła przy użyciu systemu członkostwa w usłudze ASP.NET Identity.

Aby uzyskać zaktualizowaną wersję tego samouczka korzystającego z platformy .NET Core, zobacz Potwierdzanie konta i odzyskiwanie hasła w ASP.NET Core.

Tworzenie aplikacji MVC ASP.NET

Rozpocznij od zainstalowania i uruchomienia Visual Studio Express 2013 dla sieci Web lub Visual Studio 2013. Zainstaluj Visual Studio 2013 update 3 lub nowszy.

Uwaga

Ostrzeżenie: aby ukończyć ten samouczek, musisz zainstalować Visual Studio 2013 Update 3 lub nowszej wersji.

  1. Utwórz nowy projekt ASP.NET sieci Web i wybierz szablon MVC. Web Forms obsługuje również usługę ASP.NET Identity, dzięki czemu możesz wykonać podobne kroki w aplikacji formularzy internetowych.
    Zrzut ekranu przedstawiający stronę Nowy projekt kropkowy S P. Szablon języka M V C jest zaznaczony, a poszczególne konta użytkowników są wyróżnione.

  2. Pozostaw domyślne uwierzytelnianie jako indywidualne konta użytkowników. Jeśli chcesz hostować aplikację na platformie Azure, zaznacz pole wyboru. W dalszej części samouczka wdrożymy na platformie Azure. Możesz bezpłatnie otworzyć konto platformy Azure.

  3. Ustaw projekt na użycie protokołu SSL.

  4. Uruchom aplikację, kliknij link Zarejestruj i zarejestruj użytkownika. W tym momencie jedyną weryfikacją wiadomości e-mail jest atrybut [EmailAddress].

  5. W Eksploratorze serwera przejdź do pozycji Połączenia danych\DefaultConnection\Tables\AspNetUsers, kliknij prawym przyciskiem myszy i wybierz pozycję Otwórz definicję tabeli.

    Na poniższej ilustracji AspNetUsers przedstawiono schemat:

    Zrzut ekranu przedstawiający kartę Plik skryptu użytkownika S P Net w Eksploratorze serwera.

  6. Kliknij prawym przyciskiem myszy tabelę AspNetUsers i wybierz polecenie Pokaż dane tabeli.
    Zrzut ekranu przedstawiający schemat użytkowników sieci S P. Kolumna Email Potwierdzona oznaczona jako Fałsz jest wyróżniona.
    W tym momencie wiadomość e-mail nie została potwierdzona.

  7. Kliknij wiersz i wybierz pozycję Usuń. Ta wiadomość e-mail zostanie ponownie dodana w następnym kroku i wyślesz wiadomość e-mail z potwierdzeniem.

potwierdzenie Email

Najlepszym rozwiązaniem jest potwierdzenie wiadomości e-mail nowej rejestracji użytkownika w celu sprawdzenia, czy nie personifikuje innego użytkownika (oznacza to, że nie zarejestrowali się w wiadomości e-mail innej osoby). Załóżmy, że masz forum dyskusyjne, chcesz uniemożliwić "bob@example.com" rejestrację jako "joe@contoso.com". Bez potwierdzenia "joe@contoso.com" wiadomości e-mail niepożądane wiadomości e-mail z aplikacji. Załóżmy, że Bob przypadkowo zarejestrował się jako "bib@example.com" i nie zauważył go, nie będzie mógł odzyskać hasła, ponieważ aplikacja nie ma poprawnej poczty e-mail. Email potwierdzenie zapewnia tylko ograniczoną ochronę przed botami i nie zapewnia ochrony przed określonymi spamerami, mają one wiele działających aliasów poczty e-mail, których mogą używać do rejestrowania.

Zazwyczaj chcesz uniemożliwić nowym użytkownikom publikowanie wszelkich danych w witrynie sieci Web przed potwierdzeniem ich za pomocą poczty e-mail, wiadomości SMS lub innego mechanizmu. W poniższych sekcjach włączymy potwierdzenie wiadomości e-mail i zmodyfikujemy kod, aby uniemożliwić nowo zarejestrowanym użytkownikom logowanie się do momentu potwierdzenia ich wiadomości e-mail.

Podłącz do usługi SendGrid

Instrukcje w tej sekcji nie są aktualne. Aby uzyskać zaktualizowane instrukcje, zobacz Konfigurowanie dostawcy poczty e-mail usługi SendGrid .

Chociaż w tym samouczku pokazano tylko sposób dodawania powiadomień e-mail za pośrednictwem usługi SendGrid, można wysyłać wiadomości e-mail przy użyciu protokołu SMTP i innych mechanizmów (zobacz dodatkowe zasoby).

  1. W konsoli Menedżera pakietów wprowadź następujące polecenie:

    Install-Package SendGrid
    
  2. Przejdź do strony rejestracji usługi Azure SendGrid i zarejestruj się, aby uzyskać bezpłatne konto usługi SendGrid. Skonfiguruj usługę SendGrid, dodając kod podobny do poniższego w pliku 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);
          }
       }
    }
    

Musisz dodać następujące elementy:

using SendGrid;
using System.Net;
using System.Configuration;
using System.Diagnostics;

Aby zachować ten przykład prosty, zapiszemy ustawienia aplikacji w pliku 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>

Ostrzeżenie

Zabezpieczenia — nigdy nie przechowuj poufnych danych w kodzie źródłowym. Konto i poświadczenia są przechowywane w aplikacjiSetting. Na platformie Azure można bezpiecznie przechowywać te wartości na karcie Konfigurowanie w Azure Portal. Zobacz Najlepsze rozwiązania dotyczące wdrażania haseł i innych poufnych danych w ASP.NET i na platformie Azure.

Włączanie potwierdzenia wiadomości e-mail w kontrolerze konta

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

Sprawdź, czy plik Views\Account\ConfirmEmail.cshtml ma poprawną składnię razor. ( Znak @ w pierwszym wierszu może brakować. )

@{
    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>

Uruchom aplikację i kliknij link Zarejestruj. Po przesłaniu formularza rejestracji użytkownik jest zalogowany.

Zrzut ekranu przedstawiający stronę główną My A A S P dot NET Log In .

Sprawdź swoje konto e-mail i kliknij link, aby potwierdzić twoją wiadomość e-mail.

Wymagaj potwierdzenia wiadomości e-mail przed zalogowaniem

Obecnie po zakończeniu formularza rejestracji użytkownik jest zalogowany. Zazwyczaj chcesz potwierdzić swoją wiadomość e-mail przed zalogowaniem się. W poniższej sekcji zmodyfikujemy kod, aby wymagać od nowych użytkowników potwierdzenia wiadomości e-mail przed zalogowaniem się (uwierzytelnionym). Zaktualizuj metodę HttpPost Register za pomocą następujących wyróżnionych zmian:

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

Komentując metodę SignInAsync , użytkownik nie zostanie zalogowany przez rejestrację. Wiersz TempData["ViewBagLink"] = callbackUrl; może służyć do debugowania aplikacji i testowania rejestracji bez wysyłania wiadomości e-mail. ViewBag.Message służy do wyświetlania instrukcji potwierdzenia. Przykład pobierania zawiera kod do testowania potwierdzenia wiadomości e-mail bez konfigurowania poczty e-mail, a także może służyć do debugowania aplikacji.

Views\Shared\Info.cshtml Utwórz plik i dodaj następujący znacznik razor:

@{
   ViewBag.Title = "Info";
}
<h2>@ViewBag.Title.</h2>
<h3>@ViewBag.Message</h3>

Dodaj atrybut Autoryzuj do Contact metody akcji kontrolera głównego. Możesz kliknąć link Kontakt , aby sprawdzić, czy użytkownicy anonimowi nie mają dostępu i uwierzytelnieni użytkownicy mają dostęp.

[Authorize]
public ActionResult Contact()
{
   ViewBag.Message = "Your contact page.";

   return View();
}

Należy również zaktualizować metodę HttpPost Login akcji:

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

Zaktualizuj widok Views\Shared\Error.cshtml , aby wyświetlić komunikat o błędzie:

@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>
   }
}

Usuń wszystkie konta w tabeli AspNetUsers zawierające alias e-mail, z którym chcesz przetestować. Uruchom aplikację i sprawdź, czy nie możesz się zalogować, dopóki nie potwierdzisz swojego adresu e-mail. Po potwierdzeniu adresu e-mail kliknij link Kontakt .

Odzyskiwanie/resetowanie hasła

Usuń znaki komentarza HttpPost ForgotPassword z metody akcji w kontrolerze konta:

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

Usuń znaki komentarza z linku ForgotPasswordActionLink w pliku widoku 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")
}

Na stronie Logowanie zostanie teraz wyświetlony link umożliwiający zresetowanie hasła.

Po utworzeniu nowego konta lokalnego użytkownik będzie wysyłał wiadomość e-mail na adres e-mail z linkiem potwierdzającym, którego będzie musiał użyć, zanim będzie mógł się zalogować. Jeśli użytkownik przypadkowo usunie wiadomość e-mail z potwierdzeniem lub wiadomość e-mail nigdy nie zostanie wysłana, będzie potrzebować ponownie wysłanego linku potwierdzenia. Poniższe zmiany kodu pokazują, jak to włączyć.

Dodaj następującą metodę pomocnika do dołu pliku 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;
}

Zaktualizuj metodę Register, aby użyć nowego pomocnika:

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

Zaktualizuj metodę logowania, aby ponownie wysłać hasło, jeśli konto użytkownika nie zostało potwierdzone:

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

Łączenie kont logowania społecznościowego i lokalnego

Możesz połączyć konta lokalne i społecznościowe, klikając link e-mail. W poniższej sekwencji RickAndMSFT@gmail.com najpierw jest tworzony jako identyfikator logowania lokalnego, ale możesz najpierw utworzyć konto jako dziennik społecznościowy, a następnie dodać identyfikator logowania lokalnego.

Zrzut ekranu przedstawiający stronę główną My A A S P dot Net Log In . Przykładowy element User I D został wyróżniony.

Kliknij link Zarządzaj . Zanotuj identyfikatory logowania zewnętrzne: 0 skojarzone z tym kontem.

Zrzut ekranu przedstawiający stronę My A S P dot Net Manage your account (Zarządzanie kontem). Obok wiersza Identyfikatory logowania zewnętrznego wyróżniono pozycję 0 i link Zarządzaj.

Kliknij link do innej usługi logowania i zaakceptuj żądania aplikacji. Te dwa konta zostały połączone. Będzie można zalogować się przy użyciu dowolnego konta. Możesz chcieć, aby użytkownicy dodali konta lokalne w przypadku, gdy ich usługa uwierzytelniania w dzienniku społecznościowym nie działa lub prawdopodobnie utraciła dostęp do konta społecznościowego.

Na poniższej ilustracji Tom jest dziennikiem społecznościowym (widocznym na stronie są identyfikatory logowania zewnętrzne: 1 ).

Zrzut ekranu przedstawiający stronę My A S P dot Net Manage your account (Zarządzanie kontem). Wyróżniono wiersze Wybierz hasło i zewnętrzne identyfikatory logowania.

Kliknięcie pozycji Wybierz hasło umożliwia dodanie lokalnego logowania skojarzonego z tym samym kontem.

Zrzut ekranu przedstawiający stronę My A S P dot Net Create Local Login (Utwórz lokalny identyfikator logowania). Przykładowe hasło jest wprowadzane w polach Tekst nowego hasła i Potwierdź nowe hasło.

Email potwierdzenie bardziej szczegółowo

Mój samouczek Potwierdzenie konta i odzyskiwanie hasła przy użyciu tożsamości ASP.NET zawiera ten temat z bardziej szczegółowymi informacjami.

Debugowanie aplikacji

Jeśli nie otrzymasz wiadomości e-mail zawierającej link:

  • Sprawdź folder wiadomości-śmieci lub spam.
  • Zaloguj się do konta usługi SendGrid i kliknij link działanie Email.

Aby przetestować link weryfikacyjny bez wiadomości e-mail, pobierz ukończony przykład. Na stronie zostanie wyświetlony link potwierdzenia i kody potwierdzenia.

Dodatkowe zasoby