Udostępnij za pośrednictwem


Uwierzytelnianie dwuskładnikowe za pomocą wiadomości SMS i wiadomości e-mail w systemie ASP.NET Identity

Przez Hao Kung, Pranav Rastogi, Rick Anderson, Suhas Joshi

W tym samouczku pokazano, jak skonfigurować uwierzytelnianie dwuskładnikowe (2FA) przy użyciu wiadomości SMS i poczty e-mail.

Ten artykuł został napisany przez Ricka Andersona (@RickAndMSFT), Pranav Rastogi (@rustd), Hao Kung i Suhas Joshi. Przykład NuGet został napisany głównie przez Hao Kung.

W tym temacie omówiono następujące kwestie:

Kompilowanie przykładu Identity

W tej sekcji użyjesz narzędzia NuGet, aby pobrać przykład, z którym będziemy pracować. Rozpocznij od zainstalowania i uruchomienia Visual Studio Express 2013 dla sieci Web lub Visual Studio 2013. Zainstaluj program Visual Studio 2013 Update 2 lub nowszy.

Uwaga

Ostrzeżenie: Aby ukończyć ten samouczek, musisz zainstalować program Visual Studio 2013 Update 2 .

  1. Utwórz nowy pusty projekt ASP.NET sieci Web.

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

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

    W tym samouczku użyjemy usługi SendGrid do wysyłania wiadomości e-mail i usługi Twilio lub ASPSMS na potrzeby sms-ów. Pakiet Identity.Samples instaluje kod, z którym będziemy pracować.

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

  4. Opcjonalnie: postępuj zgodnie z instrukcjami w samouczku z potwierdzeniem Email, aby podłączyć usługę SendGrid, a następnie uruchomić aplikację i zarejestrować konto e-mail.

  5. Opcjonalne: Usuń kod potwierdzenia linku demonstracyjnego wiadomości e-mail z przykładu ( ViewBag.Link kod w kontrolerze konta. DisplayEmail Zobacz metody i ForgotPasswordConfirmation akcji i widoki razor ).

  6. Opcjonalne:ViewBag.Status Usuń kod z kontrolerów zarządzanie i konta oraz z widoków Views\Account\VerifyCode.cshtml i Views\Manage\VerifyPhoneNumber.cshtml razor views. Możesz też zachować ViewBag.Status wyświetlanie, aby przetestować sposób działania tej aplikacji lokalnie bez konieczności podłączania i wysyłania wiadomości e-mail i wiadomości SMS.

Uwaga

Ostrzeżenie: Jeśli zmienisz dowolne ustawienia zabezpieczeń w tym przykładzie, aplikacje produkcyjne będą musiały przejść inspekcję zabezpieczeń, która jawnie wywołuje wprowadzone zmiany.

Konfigurowanie programu SMS na potrzeby uwierzytelniania dwuskładnikowego

Ten samouczek zawiera instrukcje dotyczące korzystania z usługi Twilio lub ASPSMS, ale możesz użyć dowolnego innego dostawcy programu SMS.

  1. Tworzenie konta użytkownika za pomocą dostawcy programu SMS

    Utwórz konto usługi Twilio lub asPSMS .

  2. Instalowanie dodatkowych pakietów lub dodawanie odwołań do usługi

    Twilio:
    W konsoli Menedżera pakietów wprowadź następujące polecenie:
    Install-Package Twilio

    ASPSMS:
    Należy dodać następujące odwołanie do usługi:

    Obraz przedstawiający okno dodawania odwołania do usługi

    Adres:
    https://webservice.aspsms.com/aspsmsx2.asmx?WSDL

    Przestrzeń nazw:
    ASPSMSX2

  3. Określanie poświadczeń użytkownika dostawcy programu SMS

    Twilio:
    Na karcie Pulpit nawigacyjny konta usługi Twilio skopiuj identyfikator SID konta i token uwierzytelniania.

    ASPSMS:
    W ustawieniach konta przejdź do pozycji Userkey i skopiuj je wraz z własnym hasłem.

    Te wartości zostaną później przechowywane w zmiennych SMSAccountIdentification i SMSAccountPassword .

  4. Określanie identyfikatora nadawcy/elementu źródłowego

    Twilio:
    Na karcie Numery skopiuj swój numer telefonu usługi Twilio.

    ASPSMS:
    W menu Odblokuj elementy źródłowe odblokuj co najmniej jedną jednostkę źródłową lub wybierz alfanumeryczną jednostkę źródłową (nieobsługiwana przez wszystkie sieci).

    Później zapiszemy tę wartość w zmiennej SMSAccountFrom .

  5. Przenoszenie poświadczeń dostawcy programu SMS do aplikacji

    Podaj poświadczenia i numer telefonu nadawcy w aplikacji:

    public static class Keys
    {
       public static string SMSAccountIdentification = "My Idenfitication";
       public static string SMSAccountPassword = "My Password";
       public static string SMSAccountFrom = "+15555551234";
    }
    

    Ostrzeżenie

    Zabezpieczenia — nigdy nie przechowuj poufnych danych w kodzie źródłowym. Konto i poświadczenia są dodawane do powyższego kodu, aby zachować prosty przykład. Zobacz ASP.NET MVC Jona Attena : Zachowaj ustawienia prywatne poza kontrolą źródła.

  6. Implementacja transferu danych do dostawcy programu SMS

    Skonfiguruj klasę SmsService w pliku App_Start\IdentityConfig.cs .

    W zależności od używanego dostawcy programu SMS aktywuj usługę Twilio lub sekcję ASPSMS :

    public class SmsService : IIdentityMessageService
    {
        public Task SendAsync(IdentityMessage message)
        {
            // Twilio Begin
            // var Twilio = new TwilioRestClient(
            //   Keys.SMSAccountIdentification,
            //   Keys.SMSAccountPassword);
            // var result = Twilio.SendMessage(
            //   Keys.SMSAccountFrom,
            //   message.Destination, message.Body
            // );
            // Status is one of Queued, Sending, Sent, Failed or null if the number is not valid
            // Trace.TraceInformation(result.Status);
            // Twilio doesn't currently have an async API, so return success.
            // return Task.FromResult(0);
            // Twilio End
    
            // ASPSMS Begin 
            // var soapSms = new WebApplication1.ASPSMSX2.ASPSMSX2SoapClient("ASPSMSX2Soap");
            // soapSms.SendSimpleTextSMS(
            //   Keys.SMSAccountIdentification,
            //   Keys.SMSAccountPassword,
            //   message.Destination,
            //   Keys.SMSAccountFrom,
            //   message.Body);
            // soapSms.Close();
            // return Task.FromResult(0);
            // ASPSMS End
        }
    }
    
  7. Uruchom aplikację i zaloguj się przy użyciu wcześniej zarejestrowanego konta.

  8. Kliknij swój identyfikator użytkownika, który aktywuje metodę Index akcji w Manage kontrolerze.

    Obraz zarejestrowanego konta zalogowanego do aplikacji

  9. Kliknij pozycję Add (Dodaj).

    Obraz przedstawiający link dodawania numeru telefonu

  10. W ciągu kilku sekund otrzymasz wiadomość SMS z kodem weryfikacyjnym. Wprowadź go i naciśnij przycisk Prześlij.

    Obraz przedstawiający wpis kodu weryfikacji telefonu

  11. Widok Zarządzaj pokazuje, że numer telefonu został dodany.

    Obraz przedstawiający okno widoku zarządzania z numerem telefonu

Analizowanie kodu

// GET: /Account/Index
public async Task<ActionResult> Index(ManageMessageId? message)
{
    ViewBag.StatusMessage =
        message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
        : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
        : message == ManageMessageId.SetTwoFactorSuccess ? "Your two factor provider has been set."
        : message == ManageMessageId.Error ? "An error has occurred."
        : message == ManageMessageId.AddPhoneSuccess ? "The phone number was added."
        : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
        : "";

    var model = new IndexViewModel
    {
        HasPassword = HasPassword(),
        PhoneNumber = await UserManager.GetPhoneNumberAsync(User.Identity.GetUserId()),
        TwoFactor = await UserManager.GetTwoFactorEnabledAsync(User.Identity.GetUserId()),
        Logins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()),
        BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(User.Identity.GetUserId())
    };
    return View(model);
}

Metoda Index akcji w Manage kontrolerze ustawia komunikat o stanie na podstawie poprzedniej akcji i udostępnia linki do zmiany hasła lokalnego lub dodania konta lokalnego. Metoda Index wyświetla również stan lub numer telefonu 2FA, identyfikatory logowania zewnętrzne, włączoną uwierzytelnianie 2FA i zapamiętaj metodę 2FA dla tej przeglądarki (wyjaśnij później). Kliknięcie identyfikatora użytkownika (e-mail) na pasku tytułu nie przekazuje wiadomości. Kliknięcie linku Numer telefonu: usuń przekazuje Message=RemovePhoneSuccess link jako ciąg zapytania.

https://localhost:44300/Manage?Message=RemovePhoneSuccess

[Obraz usuniętego numeru telefonu]

Metoda AddPhoneNumber akcji wyświetla okno dialogowe, aby wprowadzić numer telefonu, który może odbierać wiadomości SMS.

// GET: /Account/AddPhoneNumber
public ActionResult AddPhoneNumber()
{
   return View();
}

Obraz przedstawiający okno dialogowe dodawania akcji numeru telefonu

Kliknięcie przycisku Wyślij kod weryfikacyjny powoduje opublikowanie numeru telefonu do metody akcji HTTP POST AddPhoneNumber .

// POST: /Account/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }

    // Generate the token 
    var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
                               User.Identity.GetUserId(), model.Number);
    if (UserManager.SmsService != null)
    {
        var message = new IdentityMessage
        {
            Destination = model.Number,
            Body = "Your security code is: " + code
        };
        // Send token
        await UserManager.SmsService.SendAsync(message);
    }
    return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}

Metoda GenerateChangePhoneNumberTokenAsync generuje token zabezpieczający, który zostanie ustawiony w wiadomości SMS. Jeśli usługa SMS została skonfigurowana, token jest wysyłany jako ciąg "Twój kod zabezpieczeń jest <tokenem>". SmsService.SendAsync Metoda do jest wywoływana asynchronicznie, a następnie aplikacja jest przekierowywana do VerifyPhoneNumber metody akcji (która wyświetla następujące okno dialogowe), gdzie można wprowadzić kod weryfikacyjny.

Obraz przedstawiający okno dialogowe weryfikowania metody akcji numeru telefonu

Po wprowadzeniu kodu i kliknięciu przycisku prześlij kod zostanie opublikowany w metodzie akcji HTTP POST VerifyPhoneNumber .

// POST: /Account/VerifyPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);
    if (result.Succeeded)
    {
        var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
        if (user != null)
        {
            await SignInAsync(user, isPersistent: false);
        }
        return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
    }
    // If we got this far, something failed, redisplay form
    ModelState.AddModelError("", "Failed to verify phone");
    return View(model);
}

Metoda ChangePhoneNumberAsync sprawdza opublikowany kod zabezpieczeń. Jeśli kod jest poprawny, numer telefonu jest dodawany do PhoneNumber pola AspNetUsers tabeli. Jeśli to wywołanie powiedzie się, metoda jest wywoływana SignInAsync :

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
   // Clear the temporary cookies used for external and two factor sign ins
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, 
       DefaultAuthenticationTypes.TwoFactorCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties
    {
       IsPersistent = isPersistent 
    }, 
       await user.GenerateUserIdentityAsync(UserManager));
}

Parametr określa, czy sesja isPersistent uwierzytelniania jest utrwalana w wielu żądaniach.

Po zmianie profilu zabezpieczeń zostanie wygenerowana nowa sygnatura zabezpieczeń i zapisana w SecurityStamp polu tabeli AspNetUsers . Uwaga: SecurityStamp pole różni się od pliku cookie zabezpieczeń. Plik cookie zabezpieczeń nie jest przechowywany w AspNetUsers tabeli (ani nigdzie indziej w bazie danych tożsamości). Token pliku cookie zabezpieczeń jest podpisany samodzielnie przy użyciu interfejsu UserId, SecurityStampDPAPI i jest tworzony przy użyciu informacji o czasie wygaśnięcia i .

Oprogramowanie pośredniczące pliku cookie sprawdza plik cookie na każdym żądaniu. Metoda SecurityStampValidator w Startup klasie okresowo sprawdza bazę danych i sprawdza sygnaturę zabezpieczeń zgodnie z parametrem validateInterval. Dzieje się to tylko co 30 minut (w naszym przykładzie), chyba że zmienisz profil zabezpieczeń. Wybrano 30-minutowy interwał, aby zminimalizować podróże do bazy danych.

Metoda SignInAsync musi zostać wywołana po wprowadzeniu dowolnej zmiany w profilu zabezpieczeń. Gdy profil zabezpieczeń ulegnie zmianie, baza danych zostanie zaktualizowana SecurityStamp w polu i bez wywołania SignInAsync metody zostanie zalogowana tylko do momentu następnego trafienia potoku OWIN do bazy danych ().validateInterval Można to przetestować, zmieniając metodę SignInAsync , aby zwracać natychmiast, i ustawiając właściwość pliku cookie validateInterval z 30 minut na 5 sekund:

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
   return;

   // Clear any partial cookies from external or two factor partial sign ins
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie, 
       DefaultAuthenticationTypes.TwoFactorCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties
    {
       IsPersistent = isPersistent 
    }, 
       await user.GenerateUserIdentityAsync(UserManager));
}
public void ConfigureAuth(IAppBuilder app) {
    // Configure the db context, user manager and role manager to use a single instance per request
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

    // Enable the application to use a cookie to store information for the signed in user
    // and to use a cookie to temporarily store information about a user logging in with a 
    // third party login provider
    // Configure the sign in cookie
    app.UseCookieAuthentication(new CookieAuthenticationOptions {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider {
            // Enables the application to validate the security stamp when the user logs in.
            // This is a security feature which is used when you change a password or add 
            // an external login to your account.  
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                //validateInterval: TimeSpan.FromMinutes(30),
                validateInterval: TimeSpan.FromSeconds(5),
                regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
        }
    });
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

Po wprowadzeniu powyższych zmian w kodzie można zmienić profil zabezpieczeń (na przykład przez zmianę stanu Włączone dwa czynniki) i wylogować się w ciągu 5 sekund, gdy SecurityStampValidator.OnValidateIdentity metoda zakończy się niepowodzeniem. Usuń wiersz zwrotny w metodzie SignInAsync , wprowadź inną zmianę profilu zabezpieczeń i nie zostanie wylogowany. Metoda SignInAsync generuje nowy plik cookie zabezpieczeń.

Włącz uwierzytelnianie dwuskładnikowe

W przykładowej aplikacji musisz użyć interfejsu użytkownika, aby włączyć uwierzytelnianie dwuskładnikowe (2FA). Aby włączyć uwierzytelnianie 2FA, kliknij swój identyfikator użytkownika (alias adresu e-mail) na pasku nawigacyjnym. Obraz przedstawiający włączanie uwierzytelniania dwuskładnikowego
Kliknij pozycję Włącz uwierzytelnianie 2FA.Obraz po kliknięciu linku we/wy użytkownika z włączonym uwierzytelnianiem dwuskładnikowym Wyloguj się, a następnie zaloguj się ponownie. Jeśli włączono pocztę e-mail (zobacz mój poprzedni samouczek), możesz wybrać wiadomość SMS lub wiadomość e-mail dla uwierzytelniania 2FA.Obraz przedstawiający opcje wysyłania weryfikacji Zostanie wyświetlona strona Weryfikuj kod, na której można wprowadzić kod (z wiadomości SMS lub wiadomości e-mail).Obraz strony kodowej weryfikacji Kliknięcie pola wyboru Zapamiętaj tę przeglądarkę spowoduje wykluczenie konieczności użycia uwierzytelniania 2FA do zalogowania się na tym komputerze i w przeglądarce. Włączenie uwierzytelniania 2FA i kliknięcie przycisku Zapamiętaj tę przeglądarkę zapewni silną ochronę 2FA przed złośliwymi użytkownikami próbującymi uzyskać dostęp do konta, o ile nie mają dostępu do komputera. Można to zrobić na dowolnej maszynie prywatnej, której regularnie używasz. Ustawiając ustawienie Zapamiętaj tę przeglądarkę, uzyskujesz dodatkowe zabezpieczenia uwierzytelniania 2FA z komputerów, które nie są regularnie używane, i wygoda w przypadku braku konieczności przechodzenia przez uwierzytelnianie 2FA na własnych komputerach.

Jak zarejestrować dostawcę uwierzytelniania dwuskładnikowego

Podczas tworzenia nowego projektu MVC plik IdentityConfig.cs zawiera następujący kod umożliwiający zarejestrowanie dostawcy uwierzytelniania dwuskładnikowego:

public static ApplicationUserManager Create(
   IdentityFactoryOptions<ApplicationUserManager> options, 
   IOwinContext context) 
{
    var manager = new ApplicationUserManager(
       new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
    // Configure validation logic for usernames
    manager.UserValidator = new UserValidator<ApplicationUser>(manager)
    {
        AllowOnlyAlphanumericUserNames = false,
        RequireUniqueEmail = true
    };
    // Configure validation logic for passwords
    manager.PasswordValidator = new PasswordValidator
    {
        RequiredLength = 6,
        RequireNonLetterOrDigit = true,
        RequireDigit = true,
        RequireLowercase = true,
        RequireUppercase = true,
    };
    // Register two factor authentication providers. This application uses Phone and Emails as a 
    // step of receiving a code for verifying the user
    // You can write your own provider and plug it in here.
    manager.RegisterTwoFactorProvider("PhoneCode", new PhoneNumberTokenProvider<ApplicationUser>
    {
        MessageFormat = "Your security code is: {0}"
    });
    manager.RegisterTwoFactorProvider("EmailCode", new EmailTokenProvider<ApplicationUser>
    {
        Subject = "Security Code",
        BodyFormat = "Your security code is: {0}"
    });
    manager.EmailService = new EmailService();
    manager.SmsService = new SmsService();

    var dataProtectionProvider = options.DataProtectionProvider;
    if (dataProtectionProvider != null)
    {
        manager.UserTokenProvider = new DataProtectorTokenProvider<ApplicationUser>
           (dataProtectionProvider.Create("ASP.NET Identity"));
    }
    return manager;
}

Dodawanie numeru telefonu dla uwierzytelniania 2FA

AddPhoneNumber Metoda akcji w kontrolerze Manage generuje token zabezpieczający i wysyła go do podanego numeru telefonu.

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    // Generate the token and send it
    var code = await UserManager.GenerateChangePhoneNumberTokenAsync(
       User.Identity.GetUserId(), model.Number);
    if (UserManager.SmsService != null)
    {
        var message = new IdentityMessage
        {
            Destination = model.Number,
            Body = "Your security code is: " + code
        };
        await UserManager.SmsService.SendAsync(message);
    }
    return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}

Po wysłaniu tokenu nastąpi przekierowanie do VerifyPhoneNumber metody akcji, w której można wprowadzić kod w celu zarejestrowania wiadomości SMS na potrzeby 2FA. Uwierzytelnianie SMS 2FA nie jest używane do momentu zweryfikowania numeru telefonu.

Włączanie uwierzytelniania 2FA

EnableTFA Metoda akcji włącza uwierzytelnianie 2FA:

// POST: /Manage/EnableTFA
[HttpPost]
public async Task<ActionResult> EnableTFA()
{
    await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true);
    var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
    if (user != null)
    {
        await SignInAsync(user, isPersistent: false);
    }
    return RedirectToAction("Index", "Manage");
}

Należy pamiętać, że element musi być wywoływany SignInAsync , ponieważ włączenie uwierzytelniania 2FA jest zmianą profilu zabezpieczeń. Po włączeniu uwierzytelniania 2FA użytkownik będzie musiał zalogować się przy użyciu uwierzytelniania 2FA, korzystając z metod uwierzytelniania 2FA zarejestrowanych (wiadomości SMS i wiadomości e-mail w przykładzie).

Możesz dodać więcej dostawców uwierzytelniania 2FA, takich jak generatory kodu QR, lub napisać własne.

Uwaga

Kody 2FA są generowane przy użyciu jednorazowego algorytmu haseł opartego na czasie , a kody są ważne przez sześć minut. Jeśli wprowadzenie kodu potrwa dłużej niż sześć minut, zostanie wyświetlony komunikat o błędzie Nieprawidłowy kod.

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

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

Obraz przedstawiający wybieranie linku poczty e-mail

Kliknij link Zarządzaj . Zwróć uwagę na 0 zewnętrznych (identyfikatorów logowania społecznościowego) skojarzonych z tym kontem.

Obraz przedstawiający następną stronę i wybierając pozycję Zarządzaj

Kliknij link do innej usługi logowania i zaakceptuj żądania aplikacji. Te dwa konta zostały połączone. Możesz zalogować się przy użyciu jednego z tych kont. Możesz chcieć, aby użytkownicy dodawali konta lokalne na wypadek awarii usługi uwierzytelniania w sieci społecznościowej lub prawdopodobnie utracili dostęp do konta społecznościowego.

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

Obraz przedstawiający zewnętrzne identyfikatory logowania i lokalizację wybierania hasła

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

Obraz przedstawiający wybieranie strony hasła

Blokada konta przed atakami siłowymi

Konta w aplikacji można chronić przed atakami słownika, włączając blokadę użytkownika. Poniższy kod w metodzie ApplicationUserManager Create konfiguruje blokadę:

// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;

Powyższy kod włącza blokadę tylko dla uwierzytelniania dwuskładnikowego. Chociaż możesz włączyć blokadę dla logowań, zmieniając wartość shouldLockout na true w Login metodzie kontrolera konta, zalecamy, aby nie włączać blokady dla logowań, ponieważ powoduje to, że konto jest podatne na ataki logowania do systemu DOS . W przykładowym kodzie blokada jest wyłączona dla konta administratora utworzonego w metodzie ApplicationDbInitializer Seed :

public static void InitializeIdentityForEF(ApplicationDbContext db)
{
    var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
    var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
    const string name = "admin@example.com";
    const string roleName = "Admin";

    //Create Role Admin if it does not exist
    var role = roleManager.FindByName(roleName);
    if (role == null)
    {
        role = new IdentityRole(roleName);
        var roleresult = roleManager.Create(role);
    }

    var user = userManager.FindByName(name);
    if (user == null)
    {
        user = new ApplicationUser { UserName = name, Email = name };
        var result = userManager.Create(user, GetSecurePassword());
        result = userManager.SetLockoutEnabled(user.Id, false);
    }

    // Add user admin to Role Admin if not already added
    var rolesForUser = userManager.GetRoles(user.Id);
    if (!rolesForUser.Contains(role.Name))
    {
        var result = userManager.AddToRole(user.Id, role.Name);
    }
}

Wymaganie od użytkownika posiadania zweryfikowanego konta e-mail

Poniższy kod wymaga, aby użytkownik miał zweryfikowane konto e-mail, zanim będzie mógł się zalogować:

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 doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger 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 });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

Jak narzędzie SignInManager sprawdza wymaganie uwierzytelniania 2FA

Zarówno logowanie lokalne, jak i logowanie społecznościowe są sprawdzane, czy włączono uwierzytelnianie 2FA. Jeśli uwierzytelnianie 2FA jest włączone, SignInManager metoda logowania zwraca SignInStatus.RequiresVerificationwartość , a użytkownik zostanie przekierowany do SendCode metody akcji, gdzie będzie musiał wprowadzić kod w celu ukończenia logowania w sekwencji. Jeśli użytkownik ma ustawienie RememberMe na lokalnym pliku cookie użytkowników, zostanie zwrócony SignInManagerSignInStatus.Success i nie będzie musiał przechodzić przez 2FA.

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 doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger 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 });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
{
    var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
    if (loginInfo == null)
    {
        return RedirectToAction("Login");
    }

    // Sign in the user with this external login provider if the user already has a login
    var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresVerification:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            // If the user does not have an account, then prompt the user to create an account
            ViewBag.ReturnUrl = returnUrl;
            ViewBag.LoginProvider = loginInfo.Login.LoginProvider;
            return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email });
    }
}

Poniższy kod przedstawia metodę SendCode akcji. Element SelectListItem jest tworzony ze wszystkimi metodami 2FA włączonymi dla użytkownika. Element SelectListItem jest przekazywany do pomocnika DropDownListFor , który umożliwia użytkownikowi wybranie podejścia 2FA (zazwyczaj wiadomości e-mail i wiadomości SMS).

public async Task<ActionResult> SendCode(string returnUrl)
{
    var userId = await SignInManager.GetVerifiedUserIdAsync();
    if (userId == null)
    {
        return View("Error");
    }
    var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
    var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
    return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl });
}

Gdy użytkownik opublikuje metodę 2FA, wywoływana jest metoda akcji, SignInManager wysyła kod 2FA, a użytkownik jest przekierowywany do VerifyCode metody akcji, HTTP POST SendCode w której może wprowadzić kod w celu ukończenia logowania.

//
// POST: /Account/SendCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
    if (!ModelState.IsValid)
    {
        return View();
    }

    // Generate the token and send it
    if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
    {
        return View("Error");
    }
    return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl });
}

Blokada 2FA

Mimo że można ustawić blokadę konta w przypadku niepowodzeń prób logowania, takie podejście sprawia, że logowanie jest podatne na blokady systemu DOS . Zalecamy używanie blokady konta tylko z uwierzytelnianiem 2FA. Po utworzeniu ApplicationUserManager przykładowy kod ustawia blokadę 2FA i MaxFailedAccessAttemptsBeforeLockout na pięć. Po zalogowaniu się użytkownika (za pośrednictwem konta lokalnego lub konta społecznościowego) każda nieudana próba 2FA jest przechowywana i jeśli zostanie osiągnięta maksymalna liczba prób, użytkownik zostanie zablokowany przez pięć minut (możesz ustawić czas blokady za DefaultAccountLockoutTimeSpanpomocą polecenia ).

Dodatkowe zasoby