Files
Genesis_GameServer/GameServer/Program.cs
2026-09-03 19:18:46 +09:00

96 lines
4.0 KiB
C#

using Microsoft.EntityFrameworkCore;
using GameServer.Data;
using GameServer.Services;
using Scalar.AspNetCore;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.RateLimiting;
using System.Threading.RateLimiting;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
// appsettings.json에서 연결 문자열(Connection String) 가져오기
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// MariaDB(Pomelo) 엔진 등록
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
// ── 포트폴리오 1) 관리자 비밀번호 검증 도우미 (싱글턴) ───────────────────────
builder.Services.AddSingleton<PortfolioAdminAuth>();
// ── 포트폴리오 2) 관리자 전용 쿠키 인증 / 인가 정책 ─────────────────────────
builder.Services.AddAuthentication(PortfolioAdminAuth.CookieScheme)
.AddCookie(PortfolioAdminAuth.CookieScheme, o =>
{
o.Cookie.Name = "portfolio_admin";
o.Cookie.HttpOnly = true;
o.Cookie.SameSite = SameSiteMode.Lax;
o.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
o.ExpireTimeSpan = TimeSpan.FromDays(7);
o.SlidingExpiration = true;
// API 서버이므로 로그인 페이지로 리다이렉트하지 않고 상태 코드만 돌려준다.
o.Events.OnRedirectToLogin = ctx => { ctx.Response.StatusCode = 401; return Task.CompletedTask; };
o.Events.OnRedirectToAccessDenied = ctx => { ctx.Response.StatusCode = 403; return Task.CompletedTask; };
});
builder.Services.AddAuthorization(o =>
o.AddPolicy(PortfolioAdminAuth.PolicyName, p =>
p.AddAuthenticationSchemes(PortfolioAdminAuth.CookieScheme).RequireAuthenticatedUser()));
// ── 포트폴리오 3) 로그인 무차별 대입 방지: IP별 고정 창(5분에 8회) ──────────
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddPolicy("portfolio-login", httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 8,
Window = TimeSpan.FromMinutes(5),
QueueLimit = 0,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
AutoReplenishment = true
}));
});
builder.Services.AddControllers();
builder.Services.AddOpenApi();
// ── 포트폴리오 5) 이미지 업로드용 멀티파트 본문 제한 (100MB) ────────────────
builder.Services.Configure<FormOptions>(o => o.MultipartBodyLengthLimit = 100L * 1024 * 1024);
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.MapScalarApiReference();
}
app.UseHttpsRedirection();
// ── 포트폴리오 6) 정적 사이트 + 레이트 리미터 + 인증 파이프라인 ─────────────
app.UseDefaultFiles(); // "/" 요청을 index.html 로 연결 (UseStaticFiles 앞에 와야 한다)
app.UseStaticFiles();
app.UseRateLimiter();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
// ── 포트폴리오 7) SPA 딥링크 대응.
// MapControllers 뒤에 있으므로 /myGame/* 와 /api/* 라우팅을 가리지 않는다.
app.MapFallbackToFile("index.html");
// ── 포트폴리오 8) 포트폴리오 테이블 생성 + 시드.
// 실패해도 예외를 던지지 않으므로 게임 서버 부팅을 막지 않는다.
await PortfolioSchemaInitializer.EnsureCreatedAsync(app.Services,
app.Services.GetRequiredService<ILoggerFactory>().CreateLogger("PortfolioSchema"));
app.Run();