Dela via


Kontobekräftelse och lösenordsåterställning med ASP.NET identitet (C#)

Innan du gör den här självstudien bör du först slutföra Skapa en säker ASP.NET MVC 5-webbapp med inloggning, e-postbekräftelse och lösenordsåterställning. Den här självstudien innehåller mer information och visar hur du konfigurerar e-post för bekräftelse av lokalt konto och tillåter användare att återställa sitt bortglömda lösenord i ASP.NET identitet.

Ett lokalt användarkonto kräver att användaren skapar ett lösenord för kontot och att lösenordet lagras (säkert) i webbappen. ASP.NET Identity har också stöd för sociala konton, vilket inte kräver att användaren skapar ett lösenord för appen. Sociala konton använda en tredje part (till exempel Google, Twitter, Facebook eller Microsoft) för att autentisera användare. Det här avsnittet beskriver följande:

Nya användare registrerar sitt e-postalias, vilket skapar ett lokalt konto.

Bild av kontoregistreringsfönstret

Om du väljer knappen Registrera skickas ett bekräftelsemeddelande som innehåller en valideringstoken till deras e-postadress.

bild som visar e-post som skickats bekräftelse

Användaren får ett e-postmeddelande med en bekräftelsetoken för sitt konto.

Bild av bekräftelsetoken

Om du väljer länken bekräftas kontot.

Bild som bekräftar e-postadressen

Återställning/återställning av lösenord

Lokala användare som glömmer sitt lösenord kan få en säkerhetstoken skickad till sitt e-postkonto, så att de kan återställa sitt lösenord.

Bild av glömt lösenordsåterställningsfönster

Användaren får snart ett e-postmeddelande med en länk som gör att de kan återställa sitt lösenord.

bild som visar e-poståterställning av lösenord
Om du väljer länken kommer de till sidan Återställ.

Bild som visar fönstret för återställning av användarlösenord

Om du väljer knappen Återställ bekräftas att lösenordet har återställts.

Bild som visar bekräftelse av lösenordsåterställning

Skapa en ASP.NET webbapp

Börja med att installera och köra Visual Studio 2017.

  1. Skapa ett nytt ASP.NET webbprojekt och välj MVC-mallen. Webbformulär stöder även ASP.NET identitet, så du kan följa liknande steg i en webbformulärapp.

  2. Ändra autentiseringen till enskilda användarkonton.

  3. Kör appen, välj länken Registrera och registrera en användare. I det här läget är den enda verifieringen i e-postmeddelandet med attributet [EmailAddress].

  4. I Server Explorer navigerar du till Dataanslutningar\DefaultConnection\Tables\AspNetUsers, högerklickar och väljer Öppna tabelldefinition.

    Följande bild visar AspNetUsers schema:

    bild som visar A s p Net Users-schema

  5. Högerklicka på tabellen AspNetUsers och välj Visa tabelldata.

    bild som visar tabelldata

    Nu har e-postmeddelandet inte bekräftats.

Standarddatalagret för ASP.NET identitet är Entity Framework, men du kan konfigurera det för att använda andra datalager och lägga till ytterligare fält. Se avsnittet Ytterligare resurser i slutet av den här självstudien.

OWIN-startklassen ( Startup.cs ) anropas när appen startar och anropar metoden ConfigureAuth i App_Start\Startup.Auth.cs, som konfigurerar OWIN-pipelinen och initierar ASP.NET identitet. Granska metoden ConfigureAuth. Varje CreatePerOwinContext anrop registrerar ett återanrop (sparat i OwinContext) som anropas en gång per begäran för att skapa en instans av den angivna typen. Du kan ange en brytpunkt i konstruktorn och Create metod av varje typ (ApplicationDbContext, ApplicationUserManager) och kontrollera att de anropas för varje begäran. En instans av ApplicationDbContext och ApplicationUserManager lagras i OWIN-kontexten, som kan nås i hela programmet. ASP.NET Identity ansluter till OWIN-pipelinen via cookiemellanprogram. Mer information finns i Livslängdshantering per begäran för UserManager-klassen i ASP.NET Identity.

När du ändrar säkerhetsprofilen genereras en ny säkerhetsstämpel och lagras i fältet SecurityStamp i tabellen AspNetUsers. Observera att fältet SecurityStamp skiljer sig från säkerhetscookien. Säkerhetscookien lagras inte i tabellen AspNetUsers (eller någon annanstans i identitetsdatabasen). Token för säkerhetscookie är självsignerad med DPAPI- och skapas med information om UserId, SecurityStamp och förfallotid.

Cookiemellanprogrammet kontrollerar cookien på varje begäran. Metoden SecurityStampValidator i klassen Startup träffar databasen och kontrollerar säkerhetsstämpeln med jämna mellanrum, enligt validateInterval. Detta sker bara var 30:e minut (i vårt exempel) om du inte ändrar din säkerhetsprofil. Intervallet på 30 minuter valdes för att minimera resor till databasen. Se min självstudie om tvåfaktorautentisering för mer information.

Enligt kommentarerna i koden stöder metoden UseCookieAuthentication cookieautentisering. Fältet SecurityStamp och tillhörande kod ger ett extra säkerhetslager till din app. När du ändrar ditt lösenord loggas du ut från webbläsaren som du loggade in med. Med metoden SecurityStampValidator.OnValidateIdentity kan appen verifiera säkerhetstoken när användaren loggar in, vilket används när du ändrar ett lösenord eller använder den externa inloggningen. Detta krävs för att säkerställa att eventuella token (cookies) som genereras med det gamla lösenordet är ogiltiga. Om du ändrar användarnas lösenord i exempelprojektet genereras en ny token för användaren, eventuella tidigare token ogiltigförklaras och fältet SecurityStamp uppdateras.

Med identitetssystemet kan du konfigurera din app så när användarnas säkerhetsprofil ändras (till exempel när användaren ändrar sitt lösenord eller ändrar associerad inloggning (till exempel från Facebook, Google, Microsoft-konto osv.) loggas användaren ut från alla webbläsarinstanser. Bilden nedan visar till exempel exempel på enkel inloggning app, som gör att användaren kan logga ut från alla webbläsarinstanser (i det här fallet IE, Firefox och Chrome) genom att välja en knapp. Alternativt kan du med exemplet bara logga ut från en specifik webbläsarinstans.

Bild som visar exempelappfönstret för enkel utloggning

Exempel på enkel utloggningsapp visar hur ASP.NET Identity låter dig återskapa säkerhetstoken. Detta krävs för att säkerställa att eventuella token (cookies) som genereras med det gamla lösenordet är ogiltiga. Den här funktionen ger ett extra säkerhetslager för ditt program. när du ändrar ditt lösenord loggas du ut där du har loggat in på det här programmet.

Filen App_Start\IdentityConfig.cs innehåller klasserna ApplicationUserManager, EmailService och SmsService. Klasserna EmailService och SmsService implementerar var och en IIdentityMessageService-gränssnittet, så du har vanliga metoder i varje klass för att konfigurera e-post och SMS. Även om den här självstudien bara visar hur du lägger till e-postaviseringar via SendGridkan du skicka e-post med SMTP och andra mekanismer.

Den Startup-klassen innehåller också standardkod för att lägga till sociala inloggningar (Facebook, Twitter, etc.). Se min handledning om MVC 5-app med Facebook, Twitter, LinkedIn och Google OAuth2-inloggning för mer information.

Granska klassen ApplicationUserManager, som innehåller användarnas identitetsinformation och konfigurerar följande funktioner:

  • Krav på lösenordsstyrka.
  • Användarlåsning (försök och tidsintervall).
  • Tvåfaktorsautentisering (2FA). Jag ska täcka 2FA och SMS i en annan guide.
  • Koppla in e-post- och SMS-tjänsterna. (Jag ska täcka SMS i en annan handledning).

Klassen ApplicationUserManager härleds från den generiska klassen UserManager<ApplicationUser>. ApplicationUser härleds från IdentityUser. IdentityUser härleds från den generiska IdentityUser-klassen:

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

Egenskaperna ovan sammanfaller med egenskaperna i tabellen AspNetUsers, som visas ovan.

Med allmänna argument på IUser kan du härleda en klass med olika typer för primärnyckeln. Se ChangePK- exempel som visar hur du ändrar primärnyckeln från sträng till int eller GUID.

ApplicationUser

ApplicationUser (public class ApplicationUserManager : UserManager<ApplicationUser>) definieras i Models\IdentityModels.cs som:

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

Den markerade koden ovan genererar en ClaimsIdentity. ASP.NET Identity och OWIN Cookie Authentication är anspråksbaserade, därför kräver ramverket att appen genererar en ClaimsIdentity för användaren. ClaimsIdentity har information om alla anspråk för användaren, till exempel användarens namn, ålder och vilka roller användaren tillhör. Du kan också lägga till fler anspråk för användaren i det här skedet.

Metoden OWIN AuthenticationManager.SignIn skickar in ClaimsIdentity och loggar in användaren:

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

MVC 5-app med Facebook, Twitter, LinkedIn och Google OAuth2-inloggning visar hur du kan lägga till ytterligare egenskaper i klassen ApplicationUser.

E-postbekräftelse

Det är en bra idé att bekräfta e-postmeddelandet som en ny användare registrerar sig med för att kontrollera att de inte utger sig för att vara någon annan (det vill: de har inte registrerat sig hos någon annans e-post). Anta att du hade ett diskussionsforum, du vill förhindra att "bob@example.com" registreras som "joe@contoso.com". Utan e-postbekräftelse kan "joe@contoso.com" få oönskad e-post från din app. Anta att Bob av misstag registrerade sig som "bib@example.com" och inte hade märkt det, att han inte skulle kunna använda återställning av lösenord eftersom appen inte har rätt e-post. E-postbekräftelse ger endast begränsat skydd mot robotar och ger inte skydd mot bestämda spammare, de har många fungerande e-postalias som de kan använda för att registrera sig. I exemplet nedan kan användaren inte ändra sitt lösenord förrän deras konto har bekräftats (genom att välja en bekräftelselänk som tagits emot för det e-postkonto som de registrerade med.) Du kan använda det här arbetsflödet i andra scenarier, till exempel genom att skicka en länk för att bekräfta och återställa lösenordet för nya konton som skapats av administratören, och skicka ett e-postmeddelande till användaren när de har ändrat sin profil och så vidare. Du vill vanligtvis förhindra att nya användare publicerar data på din webbplats innan de har bekräftats via e-post, ett SMS eller någon annan mekanism.

Skapa ett mer komplett exempel

I det här avsnittet använder du NuGet för att ladda ned ett mer komplett exempel som vi kommer att arbeta med.

  1. Skapa ett nytt tomt ASP.NET webbprojekt.

  2. I Package Manager-konsolen anger du följande kommandon:

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

    I den här handledningen använder vi SendGrid för att skicka e-post. Det Identity.Samples paketet installerar koden som vi kommer att arbeta med.

  3. Ange att -projektet ska använda SSL-.

  4. Testa att skapa ett lokalt konto genom att köra appen, välja länken Registrera och publicera registreringsformuläret.

  5. Välj demo-e-postlänken, som simulerar e-postbekräftelse.

  6. Ta bort bekräftelsekoden för demo-e-postlänken från exemplet (koden ViewBag.Link i kontrollern för konton. Se åtgärdsmetoderna DisplayEmail och ForgotPasswordConfirmation och razor views).

Varning

Om du ändrar någon av säkerhetsinställningarna i det här exemplet måste produktionsappar genomgå en säkerhetsgranskning som uttryckligen anropar de ändringar som gjorts.

Granska koden i App_Start\IdentityConfig.cs

Exemplet visar hur du skapar ett konto och lägger till det i rollen Administratör. Du bör ersätta e-postmeddelandet i exemplet med det e-postmeddelande som du ska använda för administratörskontot. Det enklaste sättet just nu att skapa ett administratörskonto är programmatiskt i Seed-metoden. Vi hoppas ha ett verktyg i framtiden som gör att du kan skapa och administrera användare och roller. Med exempelkoden kan du skapa och hantera användare och roller, men du måste först ha ett administratörskonto för att kunna köra rollerna och användaradministratörssidorna. I det här exemplet skapas administratörskontot när databasen är seedad.

Ändra lösenordet och ändra namnet till ett konto där du kan ta emot e-postmeddelanden.

Varning

Säkerhet – Lagra aldrig känsliga data i källkoden.

Som tidigare nämnts lägger app.CreatePerOwinContext-anropet i startklassen till återkopplingar till Create-metoden för appens DB-innehåll, användarhanteraren och rollhanteraren. OWIN-pipelinen anropar metoden Create för dessa klasser för varje begäran och lagrar kontexten för varje klass. Kontokontrollanten exponerar användarhanteraren från HTTP-kontexten (som innehåller OWIN-kontexten):

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

När en användare registrerar ett lokalt konto anropas metoden 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);
}

Koden ovan använder modelldata för att skapa ett nytt användarkonto med hjälp av det angivna e-postmeddelandet och lösenordet. Om e-postaliaset finns i datalagret misslyckas kontoskapandet och formuläret visas igen. Metoden GenerateEmailConfirmationTokenAsync skapar en säker bekräftelsetoken och lagrar den i datalagret ASP.NET Identity. Metoden URL.Action skapar en länk som innehåller UserId och bekräftelsetoken. Den här länken skickas sedan via e-post till användaren, användaren kan välja på länken i sin e-postapp för att bekräfta sitt konto.

Konfigurera e-postbekräftelse

Gå till registreringssidan för SendGrid och registrera dig för ett kostnadsfritt konto. Lägg till kod som liknar följande för att konfigurera 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);
      }
   }
}

Anmärkning

E-postklienter accepterar ofta endast textmeddelanden (ingen HTML). Du bör ange meddelandet i text och HTML. I SendGrid-exemplet ovan görs detta med koden myMessage.Text och myMessage.Html som visas ovan.

Följande kod visar hur du skickar e-post med hjälp av klassen MailMessage där message.Body endast returnerar länken.

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

Varning

Säkerhet – Lagra aldrig känsliga data i källkoden. Kontot och autentiseringsuppgifterna lagras i appenInställningar. I Azure kan du lagra dessa värden på ett säkert sätt på fliken Konfigurera i Azure-portalen. Se Metodtips för att distribuera lösenord och andra känsliga data till ASP.NET och Azure.

Ange dina SendGrid-autentiseringsuppgifter, kör appen, registrera dig med ett e-postalias kan välja länken bekräfta i e-postmeddelandet. För att se hur du gör detta med ditt Outlook.com e-postkonto, se John Attens C# SMTP-konfiguration för Outlook.Com SMTP-värd och hansASP.NET Identity 2.0: Konfigurera kontoverifiering och Two-Factor auktorisering inlägg.

När en användare väljer knappen Registrera skickas ett bekräftelsemeddelande som innehåller en valideringstoken till deras e-postadress.

Bild av e-post som skickats bekräftelsefönster

Användaren får ett e-postmeddelande med en bekräftelsetoken för sitt konto.

Bild av e-post som tagits emot

Granska koden

Följande kod visar metoden 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);
}

Metoden misslyckas tyst om användarens e-post inte har bekräftats. Om ett fel har registrerats för en ogiltig e-postadress kan skadliga användare använda den informationen för att hitta giltiga användar-id:n (e-postalias) för attacker.

Följande kod visar metoden ConfirmEmail i kontokontrollanten som anropas när användaren väljer bekräftelselänken i e-postmeddelandet som skickas till dem:

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

När en bortglömd lösenordstoken har använts ogiltigförklaras den. Följande kodändring i metoden Create (i filen App_Start\IdentityConfig.cs) anger att token upphör att gälla om tre timmar.

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

Med koden ovan kommer token för att återställa bortglömda lösenord och e-postbekräftelse att förfalla om 3 timmar. Standard TokenLifespan är en dag.

Följande kod visar e-postbekräftelsemetoden:

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

För att göra din app säkrare stöder ASP.NET Identity Two-Factor autentisering (2FA). Se ASP.NET Identity 2.0: Konfigurera kontoverifiering och Two-Factor auktorisering av John Atten. Även om du kan ställa in kontoutelåsning vid inloggningslösenordförsök, gör den metoden inloggningen känslig för DOS--utelåsningar. Vi rekommenderar att du endast använder kontoutelåsning med 2FA.

Ytterligare resurser