Freigeben über


Erstellen einer sicheren ASP.NET MVC 5-Web-App mit Anmeldung, E-Mail-Bestätigung und Kennwortzurücksetzung (C#)

von Rick Anderson

In diesem Tutorial erfahren Sie, wie Sie mithilfe des ASP.NET Identity-Mitgliedschaftssystems eine ASP.NET MVC 5-Web-App mit E-Mail-Bestätigung und Kennwortzurücksetzung erstellen.

Eine aktualisierte Version dieses Tutorials, die .NET Core verwendet, finden Sie unter Kontobestätigung und Kennwortwiederherstellung in ASP.NET Core.

Erstellen einer ASP.NET MVC-App

Beginnen Sie mit der Installation und Ausführung Visual Studio Express 2013 für Web oder Visual Studio 2013. Installieren Sie Visual Studio 2013 Update 3 oder höher.

Hinweis

Warnung: Sie müssen Visual Studio 2013 Update 3 oder höher installieren, um dieses Tutorial abzuschließen.

  1. Erstellen Sie ein neues ASP.NET Webprojekt, und wählen Sie die MVC-Vorlage aus. Web Forms unterstützt auch ASP.NET Identity, sodass Sie ähnliche Schritte in einer Web Forms-App ausführen können.
    Screenshot: Seite

  2. Übernehmen Sie die Standardauthentifizierung als Individuelle Benutzerkonten. Wenn Sie die App in Azure hosten möchten, lassen Sie das Kontrollkästchen aktiviert. Später im Tutorial werden wir in Azure bereitstellen. Sie können kostenlos ein Azure-Konto eröffnen.

  3. Legen Sie das Projekt so fest, dass SSL verwendet wird.

  4. Führen Sie die App aus, klicken Sie auf den Link Registrieren , und registrieren Sie einen Benutzer. An diesem Punkt erfolgt die einzige Überprüfung der E-Mail mit dem [EmailAddress]- Attribut.

  5. Navigieren Sie unter Server Explorer zu Datenverbindungen\DefaultConnection\Tables\AspNetUsers, klicken Sie mit der rechten Maustaste, und wählen Sie Tabellendefinition öffnen aus.

    Die folgende Abbildung zeigt das AspNetUsers Schema:

    Screenshot: Registerkarte

  6. Klicken Sie mit der rechten Maustaste auf die Tabelle AspNetUsers, und wählen Sie Tabellendaten anzeigen aus.
    Screenshot: Schema
    Zu diesem Zeitpunkt wurde die E-Mail nicht bestätigt.

  7. Klicken Sie auf die Zeile, und wählen Sie Löschen aus. Sie fügen diese E-Mail im nächsten Schritt erneut hinzu und senden eine Bestätigungs-E-Mail.

bestätigung Email

Es ist eine bewährte Methode, die E-Mail-Adresse einer neuen Benutzerregistrierung zu bestätigen, um zu überprüfen, dass sie keine identitätsverändernde Person annehmen (d. a. sie haben sich nicht mit der E-Mail einer anderen Person registriert). Angenommen, Sie hätten ein Diskussionsforum und möchten verhindern "bob@example.com" , dass sie sich als "joe@contoso.com"registriert. Ohne E-Mail-Bestätigung "joe@contoso.com" könnte unerwünschte E-Mails von Ihrer App erhalten. Angenommen, Bob hat sich versehentlich als "bib@example.com" registriert und hatte es nicht bemerkt, er wäre nicht in der Lage, die Kennwortwiederherstellung zu verwenden, da die App nicht über seine richtige E-Mail verfügt. Email Bestätigung nur eingeschränkten Schutz vor Bots bietet und keinen Schutz vor bestimmten Spammern bietet, verfügen sie über viele funktionierende E-Mail-Aliase, die sie für die Registrierung verwenden können.

In der Regel möchten Sie verhindern, dass neue Benutzer Daten auf Ihrer Website veröffentlichen, bevor sie per E-Mail, SMS oder einem anderen Mechanismus bestätigt wurden. In den folgenden Abschnitten aktivieren wir die E-Mail-Bestätigung und ändern den Code, um zu verhindern, dass sich neu registrierte Benutzer anmelden, bis ihre E-Mail bestätigt wurde.

Einbinden von SendGrid

Die Anweisungen in diesem Abschnitt sind nicht aktuell. Aktualisierte Anweisungen finden Sie unter Konfigurieren des SendGrid-E-Mail-Anbieters .

Obwohl in diesem Tutorial nur gezeigt wird, wie Sie E-Mail-Benachrichtigungen über SendGrid hinzufügen, können Sie E-Mails mithilfe von SMTP und anderen Mechanismen senden (siehe zusätzliche Ressourcen).

  1. Geben Sie in der Paket-Manager-Konsole den folgenden Befehl ein:

    Install-Package SendGrid
    
  2. Navigieren Sie zur Azure SendGrid-Registrierungsseite , und registrieren Sie sich für ein kostenloses SendGrid-Konto. Konfigurieren Sie SendGrid, indem Sie Code ähnlich dem folgenden in App_Start/IdentityConfig.cs hinzufügen:

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

Sie müssen Folgendes hinzufügen:

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

Um dieses Beispiel einfach zu halten, speichern wir die App-Einstellungen in der web.config-Datei :

</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>

Warnung

Sicherheit: Speichern Sie vertrauliche Daten niemals in Ihrem Quellcode. Das Konto und die Anmeldeinformationen werden in appSetting gespeichert. In Azure können Sie diese Werte auf der Registerkarte Konfigurieren im Azure-Portal sicher speichern. Weitere Informationen finden Sie unter Bewährte Methoden für die Bereitstellung von Kennwörtern und anderen vertraulichen Daten in ASP.NET und Azure.

Aktivieren der E-Mail-Bestätigung im Kontocontroller

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

Überprüfen Sie, ob die Datei Views\Account\ConfirmEmail.cshtml die richtige Razor-Syntax aufweist. ( Das @-Zeichen in der ersten Zeile fehlt möglicherweise. )

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

Führen Sie die App aus, und klicken Sie auf den Link Registrieren. Nachdem Sie das Registrierungsformular übermittelt haben, sind Sie angemeldet.

Screenshot der Startseite

Überprüfen Sie Ihr E-Mail-Konto, und klicken Sie auf den Link, um Ihre E-Mail zu bestätigen.

E-Mail-Bestätigung vor der Anmeldung anfordern

Sobald ein Benutzer das Registrierungsformular ausgefüllt hat, ist er angemeldet. In der Regel möchten Sie ihre E-Mails bestätigen, bevor Sie sich anmelden. Im folgenden Abschnitt ändern wir den Code so, dass neue Benutzer eine bestätigte E-Mail erhalten müssen, bevor sie angemeldet (authentifiziert) werden. Aktualisieren Sie die HttpPost Register -Methode mit den folgenden hervorgehobenen Änderungen:

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

Durch auskommentieren der SignInAsync Methode wird der Benutzer von der Registrierung nicht angemeldet. Die TempData["ViewBagLink"] = callbackUrl; Zeile kann verwendet werden, um die App zu debuggen und die Registrierung zu testen, ohne E-Mails zu senden. ViewBag.Message wird verwendet, um die Bestätigungsanweisungen anzuzeigen. Das Downloadbeispiel enthält Code zum Testen der E-Mail-Bestätigung, ohne E-Mail einzurichten, und kann auch zum Debuggen der Anwendung verwendet werden.

Erstellen Sie eine Views\Shared\Info.cshtml Datei, und fügen Sie das folgende Razor-Markup hinzu:

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

Fügen Sie das Authorize-Attribut der Contact Aktionsmethode des Home-Controllers hinzu. Sie können auf den Link Kontakt klicken, um sicherzustellen, dass anonyme Benutzer keinen Zugriff haben und authentifizierte Benutzer Zugriff haben.

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

   return View();
}

Sie müssen auch die HttpPost Login Aktionsmethode aktualisieren:

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

Aktualisieren Sie die Ansicht Views\Shared\Error.cshtml , um die Fehlermeldung anzuzeigen:

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

Löschen Sie alle Konten in der Tabelle AspNetUsers , die den E-Mail-Alias enthalten, mit dem Sie testen möchten. Führen Sie die App aus, und vergewissern Sie sich, dass Sie sich erst anmelden können, wenn Sie Ihre E-Mail-Adresse bestätigt haben. Nachdem Sie Ihre E-Mail-Adresse bestätigt haben, klicken Sie auf den Link Kontakt .

Kennwortwiederherstellung/-zurücksetzung

Entfernen Sie die Kommentarzeichen aus der HttpPost ForgotPassword Aktionsmethode im Kontocontroller:

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

Entfernen Sie die Kommentarzeichen aus dem ForgotPasswordActionLink in der Razor View-Datei Views\Account\Login.cshtml :

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

Auf der Seite Anmelden wird nun ein Link zum Zurücksetzen des Kennworts angezeigt.

Sobald ein Benutzer ein neues lokales Konto erstellt hat, wird ein Bestätigungslink per E-Mail gesendet, den er verwenden muss, bevor er sich anmelden kann. Wenn der Benutzer versehentlich die Bestätigungs-E-Mail löscht oder die E-Mail nie eintrifft, muss der Bestätigungslink erneut gesendet werden. Die folgenden Codeänderungen zeigen, wie Sie dies aktivieren.

Fügen Sie unten in der Datei Controllers\AccountController.cs die folgende Hilfsmethode hinzu:

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

Aktualisieren Sie die Register-Methode, um das neue Hilfsprogramm zu verwenden:

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

Aktualisieren Sie die Login-Methode, um das Kennwort erneut zu senden, wenn das Benutzerkonto nicht bestätigt wurde:

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

Kombinieren von sozialen und lokalen Anmeldekonten

Sie können lokale und soziale Konten kombinieren, indem Sie auf Ihren E-Mail-Link klicken. In der folgenden Sequenz RickAndMSFT@gmail.com wird zuerst als lokaler Anmeldename erstellt, aber Sie können das Konto zuerst als Social Media-Anmeldung erstellen und dann eine lokale Anmeldung hinzufügen.

Screenshot: Startseite

Klicken Sie auf den Link Verwalten . Beachten Sie die externe Anmeldung: 0 , die diesem Konto zugeordnet ist.

Screenshot: Seite

Klicken Sie auf den Link zu einem anderen Anmeldedienst, und akzeptieren Sie die App-Anforderungen. Die beiden Konten wurden kombiniert. Sie können sich mit beiden Konten anmelden. Möglicherweise möchten Sie, dass Ihre Benutzer lokale Konten hinzufügen, falls ihr Authentifizierungsdienst für soziale Netzwerke ausgefallen ist oder wahrscheinlicher, dass sie den Zugriff auf ihr Social Media-Konto verloren haben.

In der folgenden Abbildung ist Tom ein Social-Media-Log-In (die Sie unter Externe Anmeldungen sehen können: 1 auf der Seite).

Screenshot: Seite

Wenn Sie auf Kennwort auswählen klicken, können Sie eine lokale Anmeldung hinzufügen, die demselben Konto zugeordnet ist.

Screenshot: Seite

ausführlichere Email Bestätigung

Mein Tutorial Kontobestätigung und Kennwortwiederherstellung mit ASP.NET Identität wird in diesem Thema mit weiteren Details behandelt.

Debuggen der App

Wenn Sie keine E-Mail mit dem Link erhalten:

  • Überprüfen Sie Ihren Junk- oder Spamordner.
  • Melden Sie sich bei Ihrem SendGrid-Konto an, und klicken Sie auf den Link Email Aktivität.

Um den Überprüfungslink ohne E-Mail zu testen, laden Sie das abgeschlossene Beispiel herunter. Der Bestätigungslink und die Bestätigungscodes werden auf der Seite angezeigt.

Zusätzliche Ressourcen