ASP.NET Identity에서 SMS 및 전자 메일을 사용한 2단계 인증
작성자: Hao Kung, Pranav Rastogi, Rick Anderson, Suhas Joshi
이 자습서에서는 SMS 및 이메일을 사용하여 2FA(2단계 인증)를 설정하는 방법을 보여 줍니다.
이 문서는 릭 앤더슨 (@RickAndMSFT), 프라나브 라스토기 (@rustd), 하오 쿵, 수하스 조시에 의해 작성되었습니다. NuGet 샘플은 주로 Hao Kung에 의해 작성되었습니다.
이 항목에서는 다음에 대해 설명합니다.
ID 샘플 빌드
이 섹션에서는 NuGet을 사용하여 작업할 샘플을 다운로드합니다. 먼저 웹 또는Visual Studio 2013 Visual Studio Express 2013을 설치하고 실행합니다. Visual Studio 2013 업데이트 2 이상을 설치합니다.
참고
경고: 이 자습서를 완료하려면 Visual Studio 2013 업데이트 2 를 설치해야 합니다.
빈 ASP.NET 웹 프로젝트를 새로 만듭니다.
패키지 관리자 콘솔에서 다음 명령을 입력합니다.
Install-Package SendGrid
Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
이 자습서에서는 SendGrid 를 사용하여 Sms 문자 메시지로 전자 메일 및 Twilio 또는 ASPSMS 를 보냅니다. 패키지는
Identity.Samples
작업할 코드를 설치합니다.SSL을 사용하도록 프로젝트를 설정합니다.
선택 사항: Email 확인 자습서의 지침에 따라 SendGrid를 연결한 다음, 앱을 실행하고 이메일 계정을 등록합니다.
선택적: 샘플에서 데모 전자 메일 링크 확인 코드를 제거합니다(
ViewBag.Link
계정 컨트롤러의 코드).DisplayEmail
및ForgotPasswordConfirmation
작업 메서드 및 razor 뷰를 참조하세요.선택적:
ViewBag.Status
관리 및 계정 컨트롤러 및 Views\Account\VerifyCode.cshtml 및 Views\Manage\VerifyPhoneNumber.cshtml razor 뷰에서 코드를 제거합니다. 또는 전자 메일 및 SMS 메시지를 연결하고 보낼 필요 없이 이 앱이 로컬에서 작동하는 방식을 테스트하기 위해 디스플레이를 유지할ViewBag.Status
수 있습니다.
참고
경고: 이 샘플에서 보안 설정을 변경하는 경우 프로덕션 앱은 변경 내용을 명시적으로 호출하는 보안 감사를 받아야 합니다.
2단계 인증을 위한 SMS 설정
이 자습서에서는 Twilio 또는 ASPSMS를 사용하기 위한 지침을 제공하지만 다른 SMS 공급자를 사용할 수 있습니다.
SMS 공급자를 사용하여 사용자 계정 만들기
추가 패키지 설치 또는 서비스 참조 추가
Twilio:
패키지 관리자 콘솔에서 다음 명령을 입력합니다.
Install-Package Twilio
ASPSMS:
다음 서비스 참조를 추가해야 합니다.주소:
https://webservice.aspsms.com/aspsmsx2.asmx?WSDL
네임스페이스:
ASPSMSX2
SMS 공급자 사용자 자격 증명 구성
Twilio:
Twilio 계정의 대시보드 탭에서 계정 SID 및 인증 토큰을 복사합니다.ASPSMS:
계정 설정에서 Userkey 로 이동하여 자체 정의 암호와 함께 복사 합니다.나중에 이러한 값을 및 변수에 저장합니다
SMSAccountIdentification
SMSAccountPassword
.SenderID/Originator 지정
Twilio:
숫자 탭에서 Twilio 전화 번호를 복사합니다.ASPSMS:
보낸 사람 잠금 해제 메뉴에서 하나 이상의 보낸 사람의 잠금을 해제하거나 영숫자 보낸 사람(모든 네트워크에서 지원되지 않음)을 선택합니다.나중에 변수에 이 값을 저장합니다
SMSAccountFrom
.SMS 공급자 자격 증명을 앱으로 전송
앱에서 자격 증명 및 보낸 사람 전화 번호를 사용할 수 있도록 합니다.
public static class Keys { public static string SMSAccountIdentification = "My Idenfitication"; public static string SMSAccountPassword = "My Password"; public static string SMSAccountFrom = "+15555551234"; }
경고
보안 - 소스 코드에 중요한 데이터를 저장하지 않습니다. 위의 코드에 계정 및 자격 증명이 추가되어 샘플을 단순하게 유지합니다. Jon Atten의 ASP.NET MVC: 프라이빗 설정을 소스 제어에서 벗어나지 않도록 유지를 참조하세요.
SMS 공급자에 대한 데이터 전송 구현
SmsService
App_Start\IdentityConfig.cs 파일에서 클래스를 구성합니다.사용된 SMS 공급자에 따라 Twilio 또는 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 } }
앱을 실행하고 이전에 등록한 계정으로 로그인합니다.
컨트롤러에서 작업 메서드를 활성화하는 사용자 ID를
Index
Manage
클릭합니다.추가를 클릭합니다.
몇 초 후에 확인 코드가 포함된 문자 메시지가 표시됩니다. 입력하고 제출을 누릅니다.
관리 보기에는 전화 번호가 추가된 것으로 표시됩니다.
코드 검사
// 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);
}
컨트롤러의 Manage
작업 메서드는 Index
이전 작업을 기반으로 상태 메시지를 설정하고 로컬 암호를 변경하거나 로컬 계정을 추가하는 링크를 제공합니다. 또한 메서드는 Index
상태 또는 2FA 전화 번호, 외부 로그인, 2FA 사용 및 이 브라우저에 대한 2FA 메서드를 표시합니다(나중에 설명). 제목 표시줄에서 사용자 ID(전자 메일)를 클릭하면 메시지가 전달되지 않습니다. 전화 번호 : 제거 링크를 클릭하면 쿼리 문자열로 전달됩니다Message=RemovePhoneSuccess
.
https://localhost:44300/Manage?Message=RemovePhoneSuccess
[]
작업 메서드는 AddPhoneNumber
SMS 메시지를 받을 수 있는 전화 번호를 입력하는 대화 상자를 표시합니다.
// GET: /Account/AddPhoneNumber
public ActionResult AddPhoneNumber()
{
return View();
}
확인 코드 보내기 단추를 클릭하면 전화 번호가 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 });
}
메서드는 GenerateChangePhoneNumberTokenAsync
SMS 메시지에 설정될 보안 토큰을 생성합니다. SMS 서비스가 구성된 경우 토큰은 "보안 코드는 토큰>"이라는 문자열로 전송됩니다<. SmsService.SendAsync
에 대한 메서드가 비동기적으로 호출된 다음 앱이 확인 코드를 입력할 VerifyPhoneNumber
수 있는 작업 메서드(다음 대화 상자를 표시함)로 리디렉션됩니다.
코드를 입력하고 제출을 클릭하면 코드가 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);
}
메서드는 ChangePhoneNumberAsync
게시된 보안 코드를 확인합니다. 코드가 올바르면 전화 번호가 테이블의 AspNetUsers
필드에 추가 PhoneNumber
됩니다. 해당 호출이 성공하면 메서드가 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));
}
매개 변수는 isPersistent
인증 세션이 여러 요청에 걸쳐 유지되는지 여부를 설정합니다.
보안 프로필을 변경하면 새 보안 스탬프가 생성되고 AspNetUsers 테이블의 필드에 저장 SecurityStamp
됩니다. SecurityStamp
필드는 보안 쿠키와 다릅니다. 보안 쿠키는 테이블(또는 ID DB의 다른 위치)에 저장 AspNetUsers
되지 않습니다. 보안 쿠키 토큰은 DPAPI 를 사용하여 자체 서명되며 및 만료 시간 정보를 사용하여 UserId, SecurityStamp
만들어집니다.
쿠키 미들웨어는 각 요청에서 쿠키를 확인합니다. 클래스의 Startup
메서드는 SecurityStampValidator
DB에 도달하고 에 지정된 validateInterval
대로 보안 스탬프를 주기적으로 확인합니다. 이는 보안 프로필을 변경하지 않는 한 30분마다(샘플에서) 발생합니다. 데이터베이스로의 여행을 최소화하기 위해 30분 간격이 선택되었습니다.
SignInAsync
보안 프로필이 변경되면 메서드를 호출해야 합니다. 보안 프로필이 변경되면 데이터베이스가 필드를 업데이트 SecurityStamp
하고 메서드를 호출 SignInAsync
하지 않으면 다음에 OWIN 파이프라인이 데이터베이스(validateInterval
)에 도달할 때까지만 로그인 상태를 유지합니다. 즉시 반환하도록 메서드를 SignInAsync
변경하고 쿠키 validateInterval
속성을 30분에서 5초로 설정하여 이를 테스트할 수 있습니다.
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);
위의 코드가 변경되면 보안 프로필을 변경할 수 있습니다(예: Two Factor Enabled 상태 변경). 메서드가 실패하면 5초 SecurityStampValidator.OnValidateIdentity
후에 로그아웃됩니다. 메서드에서 SignInAsync
반환 줄을 제거하고 다른 보안 프로필을 변경하면 로그아웃되지 않습니다. 메서드는 SignInAsync
새 보안 쿠키를 생성합니다.
2단계 인증 사용
샘플 앱에서는 UI를 사용하여 2FA(2단계 인증)를 사용하도록 설정해야 합니다. 2FA를 사용하도록 설정하려면 탐색 모음에서 사용자 ID(이메일 별칭)를 클릭합니다.
2FA 사용을 클릭합니다. 로그아웃한 다음 다시 로그인합니다. 전자 메일을 사용하도록 설정한 경우( 이전 자습서 참조) 2FA에 대한 SMS 또는 이메일을 선택할 수 있습니다. 코드 확인 페이지는 SMS 또는 전자 메일에서 코드를 입력할 수 있는 위치에 표시됩니다.이 브라우저 검사 기억 상자를 클릭하면 2FA를 사용하여 해당 컴퓨터 및 브라우저에 로그온할 필요가 없습니다. 2FA를 사용하도록 설정하고 이 브라우저 기억 을 클릭하면 컴퓨터에 액세스할 수 없는 한 계정에 액세스하려는 악의적인 사용자로부터 강력한 2FA 보호를 제공합니다. 정기적으로 사용하는 모든 개인 컴퓨터에서 이 작업을 수행할 수 있습니다. 이 브라우저 기억 을 설정하면 정기적으로 사용하지 않는 컴퓨터에서 2FA의 보안이 추가되고 사용자 컴퓨터에서 2FA를 통과하지 않아도 편리합니다.
2단계 인증 공급자를 등록하는 방법
새 MVC 프로젝트를 만들 때 IdentityConfig.cs 파일에는 2단계 인증 공급자를 등록하는 다음 코드가 포함되어 있습니다.
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;
}
2FA에 대한 전화 번호 추가
컨트롤러의 Manage
작업 메서드는 AddPhoneNumber
보안 토큰을 생성하고 제공한 전화 번호로 보냅니다.
[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 });
}
토큰을 보낸 후 2FA에 SMS를 등록하는 VerifyPhoneNumber
코드를 입력할 수 있는 작업 메서드로 리디렉션됩니다. SMS 2FA는 전화 번호를 확인할 때까지 사용되지 않습니다.
2FA 사용
작업 메서드를 EnableTFA
사용하면 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");
}
2FA 사용은 SignInAsync
보안 프로필에 대한 변경이므로 를 호출해야 합니다. 2FA를 사용하도록 설정하면 사용자가 등록한 2FA 접근 방식(샘플의 SMS 및 이메일)을 사용하여 2FA를 사용하여 로그인해야 합니다.
QR 코드 생성기와 같은 2FA 공급자를 더 추가하거나 직접 작성할 수 있습니다.
참고
2FA 코드는 시간 기반 일회용 암호 알고리즘 을 사용하여 생성되며 코드는 6분 동안 유효합니다. 코드를 입력하는 데 6분 이상 걸리는 경우 잘못된 코드 오류 메시지가 표시됩니다.
소셜 및 로컬 로그인 계정 결합
이메일 링크를 클릭하여 로컬 및 소셜 계정을 결합할 수 있습니다. 다음 시퀀스에서 "RickAndMSFT@gmail.com"는 먼저 로컬 로그인으로 만들어지지만 먼저 계정을 소셜 로그로 만든 다음 로컬 로그인을 추가할 수 있습니다.
관리 링크를 클릭합니다. 이 계정과 연결된 외부(소셜 로그인)가 0개임을 확인합니다.
다른 로그인 서비스에 대한 링크를 클릭하고 앱 요청을 수락합니다. 두 계정이 결합되어 두 계정 중 하나로 로그온할 수 있습니다. 인증 서비스의 소셜 로그가 다운되거나 소셜 계정에 대한 액세스 권한이 손실된 경우 사용자가 로컬 계정을 추가하도록 할 수 있습니다.
다음 이미지에서 Tom은 소셜 로그인입니다( 외부 로그인에서 볼 수 있음: 페이지에 표시된 1).
암호 선택을 클릭하면 동일한 계정과 연결된 로컬 로그온을 추가할 수 있습니다.
무차별 암호 대입 공격으로부터 계정 잠금
사용자 잠금을 사용하도록 설정하여 사전 공격으로부터 앱의 계정을 보호할 수 있습니다. 메서드의 다음 코드는 ApplicationUserManager Create
잠금을 구성합니다.
// Configure user lockout defaults
manager.UserLockoutEnabledByDefault = true;
manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
manager.MaxFailedAccessAttemptsBeforeLockout = 5;
위의 코드는 2단계 인증에 대해서만 잠금을 사용하도록 설정합니다. 계정 컨트롤러의 메서드에서 Login
true로 변경 shouldLockout
하여 로그인 잠금을 사용하도록 설정할 수 있지만 DOS 로그인 공격에 취약한 계정을 만들기 때문에 로그인에 대해 잠금을 사용하도록 설정하지 않는 것이 좋습니다. 샘플 코드에서 메서드에서 만든 관리자 계정에 대해 잠금이 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);
}
}
사용자에게 유효성이 검사된 전자 메일 계정을 갖도록 요구
다음 코드에서는 사용자가 로그인하기 전에 유효성이 검사된 전자 메일 계정을 갖도록 요구합니다.
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);
}
}
SignInManager가 2FA 요구 사항을 확인하는 방법
검사 로컬 로그인 및 소셜 로그를 모두 사용하여 2FA가 사용하도록 설정되어 있는지 확인합니다. 2FA를 사용하도록 설정 SignInManager
하면 로그온 메서드가 를 반환 SignInStatus.RequiresVerification
하고 사용자가 작업 메서드로 SendCode
리디렉션됩니다. 여기서 로그를 순서대로 완료하기 위해 코드를 입력해야 합니다. 사용자에게 RememberMe가 사용자 로컬 쿠키에 설정된 경우 가 SignInManager
반환 SignInStatus.Success
되며 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 });
}
}
다음 코드는 작업 메서드를 SendCode
보여줍니다. SelectListItem은 사용자에 대해 사용하도록 설정된 모든 2FA 메서드를 사용하여 만들어집니다. SelectListItem은 DropDownListFor 도우미에 전달되므로 사용자가 2FA 접근 방식(일반적으로 전자 메일 및 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 });
}
사용자가 2FA 접근 방식을 HTTP POST SendCode
게시하면 작업 메서드가 호출되고, SignInManager
는 2FA 코드를 보내고, 사용자는 코드를 입력하여 로그인을 완료할 VerifyCode
수 있는 작업 메서드로 리디렉션됩니다.
//
// 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 });
}
2FA 잠금
로그인 암호 시도 실패 시 계정 잠금을 설정할 수 있지만 이 방법을 사용하면 로그인이 DOS 잠금에 취약해집니다. 2FA에서만 계정 잠금을 사용하는 것이 좋습니다. 가 ApplicationUserManager
만들어지면 샘플 코드는 2FA 잠금을 설정하고 MaxFailedAccessAttemptsBeforeLockout
5로 설정합니다. 사용자가 로컬 계정 또는 소셜 계정을 통해 로그인하면 2FA에서 실패한 각 시도가 저장되고 최대 시도에 도달하면 사용자가 5분 동안 잠깁니다(로 잠금 시간을 DefaultAccountLockoutTimeSpan
설정할 수 있음).
추가 리소스
- ASP.NET ID 권장 리소스 ID 블로그, 비디오, 자습서 및 훌륭한 SO 링크의 전체 목록입니다.
- Facebook, Twitter, LinkedIn 및 Google OAuth2 로그온이 포함된 MVC 5 앱 은 사용자 테이블에 프로필 정보를 추가하는 방법도 보여줍니다.
- ASP.NET MVC 및 ID 2.0: John Atten의 기본 사항 이해
- ASP.NET ID를 사용하여 계정 확인 및 암호 복구
- ASP.NET Identity 소개
- Pranav Rastogi가 ASP.NET Identity 2.0.0의 RTM을 발표합니다.
- ASP.NET ID 2.0: John Atten의 계정 유효성 검사 및 Two-Factor 권한 부여 설정