재우니의 블로그

 

ASP.NET Core 애플리케이션을 클러스터링(Network Load Balancing, NLB) 환경에서 운영할 때, 세션(Session) 또는 전역(Global) 저장소(Cache)를 활용하여 인증 정보를 저장하면 몇 가지 문제가 발생할 수 있습니다. 이 문제들이 왜 발생하는지, 그리고 이를 해결하기 위한 대안을 정리해 보겠습니다.

 

1️⃣ NLB(네트워크 로드 밸런싱)에서 발생하는 문제점

클러스터링된 환경에서 여러 개의 웹 서버가 동시에 작동할 때, 인증 정보를 관리하는 방식에 따라 세션 관리와 데이터 동기화에 문제가 생길 수 있습니다.

🔹 문제 1: 세션(Session) 기반 인증 정보 사용 시 문제

ASP.NET Core에서 Session을 활용하여 인증 정보를 저장하는 경우, 사용자가 최초 로그인한 서버에만 세션이 저장됩니다.
그러나 NLB 환경에서는 요청이 특정 서버에 할당된다는 보장이 없으므로, 다른 서버로 요청이 라우팅될 경우 인증 정보가 없는 상태가 됩니다.

📌 예시

  1. 사용자가 Server A에서 로그인 → 세션이 Server A의 메모리에 저장됨
  2. NLB가 요청을 Server B로 보냄 → Server B에서는 세션 정보가 없음
  3. 사용자는 로그아웃된 상태처럼 보이거나, 다시 로그인해야 함

❌ 발생 문제: 세션이 개별 서버에 저장되기 때문에 사용자가 이동할 때 인증 정보가 사라지는 문제 발생


🔹 문제 2: 전역 캐시(Cache) 사용 시 문제

MemoryCache, Static Variable, Singleton Pattern을 이용하여 서버의 메모리에 인증 정보를 저장하는 경우,
이 정보는 해당 서버에서만 유효하므로, 요청이 다른 서버로 전달되면 인증이 끊길 수 있습니다.

📌 예시

  • MemoryCache를 사용하여 JWT 토큰을 저장
  • 사용자가 로그인한 후 Server A의 캐시에 토큰을 저장
  • 다음 요청이 Server B로 라우팅되면 Server B에는 캐시된 정보가 없음
  • 사용자는 다시 로그인해야 하는 문제가 발생

❌ 발생 문제: 서버 간 캐시 정보가 공유되지 않아서 인증 상태가 유지되지 않음


🔹 문제 3: Global 같은 전역 변수 활용 시 문제

일부 개발자들이 static 변수를 사용하여 전역적으로 인증 정보를 관리하는 경우가 있는데,
이 경우에도 클러스터 환경에서는 각 서버마다 개별적인 메모리를 사용하므로 데이터 불일치(Inconsistency) 문제가 발생합니다.

 

public static class GlobalAuthStore
{
    public static Dictionary<string, string> AuthTokens = new Dictionary<string, string>();
}

 

  • 사용자가 Server A에서 로그인하여 GlobalAuthStore에 인증 정보를 저장
  • 요청이 Server B로 전달되면, Server B에서는 해당 사용자의 인증 정보를 찾을 수 없음

❌ 발생 문제: 전역 변수는 서버 간 공유되지 않음 → 인증 정보가 유지되지 않음


2️⃣ 해결책 (Best Practices)

이러한 문제를 해결하기 위해, 공유 가능한 인증 관리 기법을 도입해야 합니다. 아래 대안들을 적용하면 클러스터링된 환경에서도 인증 정보가 정상적으로 유지됩니다.


🔹 대안 1: 분산 세션 저장소 (Distributed Session Storage) 사용

기본적으로 ASP.NET Core의 세션(ISession)은 단일 서버의 메모리에 저장되므로 클러스터 환경에서는 제대로 동작하지 않습니다. 이를 해결하려면 분산 세션 저장소를 사용해야 합니다.

📌 추천 솔루션

  • Redis 기반 세션 저장소 (Microsoft.Extensions.Caching.StackExchangeRedis)
  • SQL Server 기반 세션 저장소 (Microsoft.AspNetCore.Session)

📌 설정 방법 (Redis 예제)

 

public void ConfigureServices(IServiceCollection services)
{
    services.AddStackExchangeRedisCache(options =>
    {
        options.Configuration = "localhost:6379"; // Redis 서버 주소
        options.InstanceName = "MyAppSession";
    });

    services.AddSession(options =>
    {
        options.IdleTimeout = TimeSpan.FromMinutes(30);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });
}

 

 

이점

  • 세션이 Redis에 저장되므로, 모든 서버에서 동일한 세션을 공유 가능
  • Sticky Session 없이도 정상 동작

🔹 대안 2: 중앙 인증 서버 (Identity Server, OAuth, OpenID Connect)

클러스터 환경에서 세션이 아닌 **토큰 기반 인증(JWT, OAuth 2.0, OpenID Connect)**을 사용하면 서버 간 인증 정보 공유 문제가 해결됩니다.

📌 추천 솔루션

  • OAuth 2.0 & OpenID Connect (Identity Server 4, Auth0, Okta 등)
  • JWT(JSON Web Token) 기반 인증

📌 JWT 인증 예제 (ASP.NET Core)

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.Authority = "https://auth.example.com"; // 인증 서버
            options.Audience = "my-api";
        });
}

 

이점

  • 클라이언트가 JWT를 가지고 있으므로 상태를 유지할 필요 없음
  • 서버 간 세션 공유가 필요하지 않음
  • 수평 확장(Scaling) 가능

🔹 대안 3: Sticky Session (세션 고정) 사용

로드 밸런서에서 **Sticky Session(세션 고정)**을 활성화하면, 사용자의 모든 요청이 최초 로그인한 서버로만 전달됩니다.

📌 설정 방법

  • Nginx: ip_hash 또는 sticky 옵션 사용
  • AWS ALB(Application Load Balancer): Session Stickiness 설정

이점

  • 별도의 세션 공유가 필요 없음
  • 단일 서버에서 처리되므로 속도가 빠름

단점

  • 특정 서버가 과부하될 가능성이 있음
  • 서버 장애 발생 시 세션 유지가 어려움

결론

NLB 환경에서 ASP.NET Core에서 인증 정보를 세션이나 글로벌 변수로 관리하면 데이터 불일치 문제가 발생할 수 있습니다.
이를 방지하기 위해 아래 해결책을 적용하는 것이 좋습니다.

📌 추천 대안 (우선순위)

  1. JWT + OAuth(OpenID Connect) 기반 인증 사용 (가장 추천)
  2. Redis 기반 분산 세션 저장소 사용 (세션 유지 필요 시)
  3. 로드 밸런서 Sticky Session 활성화 (긴급 대안)

이 방법들을 적용하면 NLB 및 클러스터 환경에서도 인증 정보가 안정적으로 유지되며, 확장성(Scalability)과 가용성(High Availability)이 향상됩니다. 🚀

 


 

 

 

좀더, ASP.NET Core 8 환경에서 클러스터나 NLB(Network Load Balancer) 환경에서 인증 정보를 세션이나 메모리 캐시(예, In-Memory Cache, Global 변수 등)를 통해 관리할 경우 발생할 수 있는 문제와 그 해결책에 대해 알아보죠.


1. 문제점: In-Memory 기반 인증 정보 공유 방식의 한계

1.1. 세션(Session) 기반 인증 정보

  • 문제점:
    • 기본 세션 저장 방식은 각 서버의 메모리에 저장되므로, 사용자가 최초 로그인한 서버(예, Server A)에만 세션 정보가 존재합니다.
    • 클러스터 환경 또는 NLB에서 요청이 다른 서버(예, Server B)로 라우팅되면, 해당 서버에는 세션 정보가 없으므로 인증 상태가 깨지게 됩니다.

1.2. 전역 변수(Global)나 In-Memory Cache 사용

  • 문제점:
    • MemoryCache, static 변수 등을 사용하여 인증 정보를 저장할 경우, 이 정보는 해당 서버 인스턴스에서만 유지됩니다.
    • 클러스터 환경에서 서버 간 메모리 공유가 이루어지지 않으므로, 서버 간 데이터 불일치(Inconsistency) 문제가 발생하게 됩니다.

2. 해결책: 상태 공유 및 상태 비저장(State-less) 인증 방식

2.1. 분산 세션 저장소(Distributed Session Storage) 사용 (Redis 추천)

  • 설명:
    • Redis와 같은 분산 캐시 서버를 세션 저장소로 사용하면 모든 서버가 동일한 세션 정보를 공유할 수 있습니다.
    • ASP.NET Core 8에서는 최소 호스팅 모델(Program.cs)로 설정하는 경우도 많이 사용됩니다.
  • 코드 예제 (Program.cs - Redis 기반 분산 세션):

 

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;

var builder = WebApplication.CreateBuilder(args);

// Redis 캐시 서비스 등록 (StackExchange.Redis)
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379"; // Redis 서버 주소 (환경에 맞게 변경)
    options.InstanceName = "MyAppSession_";
});

// 분산 세션 서비스 등록
builder.Services.AddSession(options =>
{
    options.IdleTimeout = TimeSpan.FromMinutes(30); // 세션 만료 시간
    options.Cookie.HttpOnly = true;
    options.Cookie.IsEssential = true;
});

// MVC 혹은 컨트롤러 서비스 추가 (필요에 따라)
builder.Services.AddControllersWithViews();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
}

app.UseStaticFiles();
app.UseRouting();

// 세션 미들웨어 등록 (인증보다 먼저 실행)
app.UseSession();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

 

 

 

설명:

  • AddStackExchangeRedisCache로 Redis 캐시를 설정하면, 각 서버가 Redis 서버를 통해 동일한 세션 데이터를 읽고 쓸 수 있습니다.
  • AddSession과 app.UseSession()을 통해 세션 미들웨어를 등록함으로써, 모든 서버가 분산 세션 저장소를 사용하게 됩니다.

 

 

2.2. JWT 기반 인증 사용 (Stateless 인증 방식)

  • 설명:
    • JWT(JSON Web Token)를 사용하면, 인증 정보를 클라이언트에 저장하고, 서버는 토큰의 유효성만 검증하면 되므로 별도의 상태(세션)를 공유할 필요가 없습니다.
    • 이는 클러스터 환경에서 서버 간 데이터 동기화 문제를 근본적으로 해결해 줍니다.
  • 코드 예제 (Program.cs - JWT 기반 인증):
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);

// JWT 인증 서비스 등록
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://yourdomain.com",      // 실제 발급자 (환경에 맞게 변경)
            ValidAudience = "yourAudience",              // 수신자 식별자
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your-secret-key")) // 비밀 키
        };
    });

builder.Services.AddControllers();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Home/Error");
}

app.UseRouting();

// 인증 및 인가 미들웨어 등록
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

 

 

 

설명:

  • AddAuthentication과 AddJwtBearer를 사용하여 JWT 기반 인증을 구성합니다.
  • 클라이언트는 로그인 후 JWT 토큰을 받아 모든 요청에 포함시키므로, 서버는 별도의 상태 저장 없이 토큰의 유효성을 검증합니다.

 

 

2.3. Sticky Session 사용 (임시 대안)

  • 설명:
    • 로드 밸런서에서 Sticky Session(세션 고정) 기능을 활성화하면 사용자의 모든 요청이 최초 로그인한 동일한 서버로 전달됩니다.
    • 이 방법은 추가적인 상태 공유 없이 문제를 회피할 수 있지만, 특정 서버에 부하가 집중되거나 서버 장애 시 문제가 발생할 수 있으므로 임시 대안으로 권장됩니다.
  • 설정 방법:
    • Nginx: ip_hash 혹은 sticky 모듈 사용
    • AWS ALB: Session Stickiness 설정
    • Azure Application Gateway: 세션 유지 설정 사용

결론

  • 문제 요약:
    • ASP.NET Core 8 환경에서 세션이나 전역(In-Memory) 캐시를 사용할 경우, 클러스터 또는 NLB 환경에서 서버 간에 인증 정보가 공유되지 않아 데이터 불일치 및 인증 문제 발생
  • 대안:
    1. 분산 세션 저장소 (Redis 기반)
      • 모든 서버가 동일한 Redis 인스턴스를 참조하여 세션을 공유
      • 위의 코드 예제를 참고
    2. JWT 기반 인증 (Stateless 방식)
      • 클라이언트가 토큰을 보유하여 서버 간 상태 공유 불필요
      • 위의 코드 예제를 참고
    3. Sticky Session 사용 (임시 대안)
      • 로드 밸런서에서 사용자 요청을 동일 서버로 고정

이와 같이 최신 ASP.NET Core 8 환경에 맞게 코드를 구성하면, 클러스터링 및 NLB 환경에서도 안정적인 인증 정보를 관리할 수 있습니다. 이 자료를 기반으로 개발자와 커뮤니케이션하면 구체적인 코드와 해결책을 명확히 전달할 수 있을 것입니다.

 

 

 

 

 

Buy Me A Coffee