✨ ASP.NET Core IdentityOptions 개요
ASP.NET Core의 IdentityOptions는 시스템의 설정을 한 곳에 몰아서 관리하는 클래스입니다. (중앙 집중식 구성)
이를 통해 패스워드 정책, 사용자 설정, 로그인 정책, 잠금 정책 등을 세밀하게 제어할 수 있습니다.
🔥 주요 구성 요소

1️⃣ Password 옵션 (PasswordOptions)
패스워드 복잡성 요구사항을 설정합니다.
// Program.cs 또는 Startup.cs // 개발 환경용 - 간단한 패스워드 public void ConfigureDevelopmentPassword(IdentityOptions options) { options.Password.RequireDigit = false; options.Password.RequireLowercase = false; options.Password.RequireUppercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequiredLength = 4; } // 프로덕션 환경용 - 강력한 패스워드 public void ConfigureProductionPassword(IdentityOptions options) { options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.Password.RequireUppercase = true; options.Password.RequireNonAlphanumeric = true; options.Password.RequiredLength = 12; options.Password.RequiredUniqueChars = 4; }
2️⃣ Lockout 옵션 (LockoutOptions)
계정 잠금 정책을 설정합니다.
public void ConfigureProgressiveLockout(IdentityOptions options) { options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1); // 1분부터 시작 options.Lockout.MaxFailedAccessAttempts = 3; // 3회 실패시 잠금 options.Lockout.AllowedForNewUsers = true; } // 커스텀 잠금 시간 증가 로직 (서비스에서 구현) public class CustomLockoutService { public TimeSpan CalculateLockoutDuration(int failedAttempts) { return failedAttempts switch { <= 3 => TimeSpan.FromMinutes(5), <= 6 => TimeSpan.FromMinutes(15), <= 10 => TimeSpan.FromHours(1), _ => TimeSpan.FromHours(24) }; } } public void ConfigureProgressiveLockout(IdentityOptions options) { options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1); // 1분부터 시작 options.Lockout.MaxFailedAccessAttempts = 3; // 3회 실패시 잠금 options.Lockout.AllowedForNewUsers = true; } // 커스텀 잠금 시간 증가 로직 (서비스에서 구현) public class CustomLockoutService { public TimeSpan CalculateLockoutDuration(int failedAttempts) { return failedAttempts switch { <= 3 => TimeSpan.FromMinutes(5), <= 6 => TimeSpan.FromMinutes(15), <= 10 => TimeSpan.FromHours(1), _ => TimeSpan.FromHours(24) }; } }
3️⃣ SignIn 옵션 (SignInOptions)
로그인 관련 설정을 구성합니다.
// 이메일 인증 필수 설정 public void ConfigureEmailConfirmation(IdentityOptions options) { options.SignIn.RequireConfirmedEmail = true; } // 회원가입 컨트롤러 [HttpPost] public async Task<IActionResult> Register(RegisterViewModel model) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { // 이메일 인증 토큰 생성 var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); var confirmationLink = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, token = token }, Request.Scheme); // 이메일 발송 await _emailSender.SendEmailAsync(model.Email, "이메일 인증", $"다음 링크를 클릭하여 계정을 활성화하세요: {confirmationLink}"); return View("EmailConfirmationSent"); } return View(model); } // 이메일 인증 필수 설정 public void ConfigureEmailConfirmation(IdentityOptions options) { options.SignIn.RequireConfirmedEmail = true; } // 회원가입 컨트롤러 [HttpPost] public async Task<IActionResult> Register(RegisterViewModel model) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { // 이메일 인증 토큰 생성 var token = await _userManager.GenerateEmailConfirmationTokenAsync(user); var confirmationLink = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, token = token }, Request.Scheme); // 이메일 발송 await _emailSender.SendEmailAsync(model.Email, "이메일 인증", $"다음 링크를 클릭하여 계정을 활성화하세요: {confirmationLink}"); return View("EmailConfirmationSent"); } return View(model); }
4️⃣ User 옵션 (UserOptions)
사용자 계정 관련 설정을 구성합니다.
builder.Services.Configure<IdentityOptions>(options => { // 사용자 설정 options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; options.User.RequireUniqueEmail = true; // 중복 이메일 방지 }); 사용자명 정책 예제: public void ConfigureUserPolicies(IdentityOptions options) { // 한국어 사용자명 허용 options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+가-힣"; // 이메일을 고유하게 설정 options.User.RequireUniqueEmail = true; } // 커스텀 사용자명 검증 public class CustomUserValidator<TUser> : IUserValidator<TUser> where TUser : class { public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager, TUser user) { var userName = manager.GetUserNameAsync(user).Result; // 사용자명이 숫자로만 구성되면 안됨 if (userName.All(char.IsDigit)) { return Task.FromResult( IdentityResult.Failed(new IdentityError { Code = "NumericUserName", Description = "사용자명은 숫자로만 구성될 수 없습니다." })); } return Task.FromResult(IdentityResult.Success); } }
5️⃣ Stores 옵션 (StoreOptions)
사용자 계정 관련 설정을 구성합니다.
builder.Services.Configure<IdentityOptions>(options => { // 저장소 설정 options.Stores.MaxLengthForKeys = 128; // 키 최대 길이 options.Stores.ProtectPersonalData = false; // 개인정보 암호화 여부 });
🐱 실제 활용 시나리오
1️⃣ 다단계 인증 설정
public void ConfigureTwoFactorAuthentication(IdentityOptions options) { options.SignIn.RequireConfirmedPhoneNumber = true; // 전화번호 인증 필수 options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider; } // 2FA 활성화 코드 [HttpPost] public async Task<IActionResult> EnableTwoFactorAuthentication() { var user = await _userManager.GetUserAsync(User); await _userManager.SetTwoFactorEnabledAsync(user, true); return RedirectToAction("ShowRecoveryCodes"); }
2️⃣ 소셜 로그인과 연계
public void ConfigureExternalLogins(IdentityOptions options) { // 외부 로그인 시에는 이메일 인증 건너뛰기 options.SignIn.RequireConfirmedEmail = false; // 하지만 고유 이메일은 필수 options.User.RequireUniqueEmail = true; } // 외부 로그인 처리 [HttpPost] public IActionResult ExternalLogin(string provider) { var redirectUrl = Url.Action("ExternalLoginCallback"); var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); return Challenge(properties, provider); }
3️⃣ 커스텀 검증 규칙 적용
// 커스텀 패스워드 검증기 public class CustomPasswordValidator : IPasswordValidator<ApplicationUser> { public Task<IdentityResult> ValidateAsync(UserManager<ApplicationUser> manager, ApplicationUser user, string password) { // 사용자명과 패스워드가 너무 유사하면 안됨 if (password.Contains(user.UserName, StringComparison.OrdinalIgnoreCase)) { return Task.FromResult(IdentityResult.Failed(new IdentityError { Code = "PasswordContainsUserName", Description = "패스워드에 사용자명이 포함될 수 없습니다." })); } return Task.FromResult(IdentityResult.Success); } } // 서비스 등록 builder.Services.AddTransient<IPasswordValidator<ApplicationUser>, CustomPasswordValidator>();
❇️ 참고 사항
1️⃣ 모니터링 및 로깅
보안 이벤트 로깅
public class SecurityEventLogger { private readonly ILogger<SecurityEventLogger> _logger; public SecurityEventLogger(ILogger<SecurityEventLogger> logger) { _logger = logger; } public void LogFailedLogin(string userName, string ipAddress) { _logger.LogWarning("Failed login attempt for user {UserName} from IP {IpAddress}", userName, ipAddress); } public void LogAccountLocked(string userName) { _logger.LogWarning("Account locked for user {UserName}", userName); } }
2️⃣ 환경별 설정 관리
appsettings.json을 활용한 설정
{ "Identity": { "Password": { "RequiredLength": 8, "RequireDigit": true, "RequireUppercase": true, "RequireNonAlphanumeric": false }, "Lockout": { "MaxFailedAccessAttempts": 5, "DefaultLockoutTimeSpanMinutes": 15 }, "SignIn": { "RequireConfirmedEmail": true } } }
// 설정 바인딩 public class IdentityConfig { public PasswordConfig Password { get; set; } public LockoutConfig Lockout { get; set; } public SignInConfig SignIn { get; set; } } // Program.cs에서 적용 var identityConfig = builder.Configuration.GetSection("Identity").Get<IdentityConfig>(); builder.Services.Configure<IdentityOptions>(options => { options.Password.RequiredLength = identityConfig.Password.RequiredLength; options.Password.RequireDigit = identityConfig.Password.RequireDigit; // ... 기타 설정 });