using SharedKernel; using Infrastructure.Persistence; using Infrastructure.Persistence.Identity; using Infrastructure.Extensions; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Caching.Distributed; using System.Net; using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork; namespace Admin.Extensions { public static class ServiceCollectionExtensions { public static void AddAdminForwardedHeaders(this IServiceCollection services, AppSettings settings) { services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; options.ForwardLimit = settings.ForwardedHeaders.ForwardLimit <= 0 ? 1 : settings.ForwardedHeaders.ForwardLimit; // 설정에서 KnownProxies / KnownNetworks 읽기 var knownProxies = settings.ForwardedHeaders.KnownProxies; if (knownProxies != null) { foreach (var proxy in knownProxies) { if (IPAddress.TryParse(proxy, out var ip)) { options.KnownProxies.Add(ip); } } } var knownNetworks = settings.ForwardedHeaders.KnownNetworks; if (knownNetworks != null) { foreach (var net in knownNetworks) { var parts = net.Split('/'); if (parts.Length == 2 && IPAddress.TryParse(parts[0], out var networkIp) && int.TryParse(parts[1], out var prefix)) { options.KnownNetworks.Add(new IPNetwork(networkIp, prefix)); } } } }); } // 관리자단 비밀번호 정책 구성 public static void AddAdminIdentity(this IServiceCollection services, AppSettings settings) { services.AddIdentity(options => { options.SignIn.RequireConfirmedAccount = true; // 이메일 확인 활성화 // Password settings. options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.Password.RequireNonAlphanumeric = true; options.Password.RequireUppercase = true; options.Password.RequiredLength = 6; options.Password.RequiredUniqueChars = 1; // Lockout settings. options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); options.Lockout.MaxFailedAccessAttempts = 5; options.Lockout.AllowedForNewUsers = true; // User settings. options.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+"; options.User.RequireUniqueEmail = true; }) .AddDefaultUI() // 기본 UI 사용 .AddDefaultTokenProviders() // 기본 토큰 제공자 사용 .AddEntityFrameworkStores(); // 사용자 계정 저장소 // Identity Cookie Session Store 설정 services.AddSingleton(sp => new DistributedCacheTicketStore( sp.GetRequiredService(), keyPrefix: settings.Redis.AuthTicketPrefix ) ); services.AddOptions(IdentityConstants.ApplicationScheme).Configure((options, ticketStore) => { options.SessionStore = ticketStore; }); services.ConfigureApplicationCookie(options => { // Cookie settings options.ExpireTimeSpan = TimeSpan.FromMinutes(30); options.LoginPath = "/Identity/Account/Login"; options.AccessDeniedPath = "/Identity/Account/AccessDenied"; options.SlidingExpiration = true; options.Events.OnRedirectToLogin = context => { if (context.Request.Path.StartsWithSegments("/Api")) { context.Response.StatusCode = StatusCodes.Status401Unauthorized; return Task.CompletedTask; } context.Response.Redirect(context.RedirectUri); // 기존 쿠키 인증 흐름 유지 return Task.CompletedTask; }; options.Events.OnRedirectToAccessDenied = context => { if (context.Request.Path.StartsWithSegments("/Api")) { context.Response.StatusCode = StatusCodes.Status403Forbidden; return Task.CompletedTask; } context.Response.Redirect(context.RedirectUri); return Task.CompletedTask; }; }); } } }