ASP.NET Core에서 Public vs Private API — 미들웨어 파이프라인 분기 (프로덕션 지향, 미소와 함께)

발행: (2026년 1월 20일 오전 05:20 GMT+9)
15 min read
원문: Dev.to

I’m ready to translate the article for you, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you’ve already provided) so I can convert it to Korean while preserving the formatting?

목차

  1. 한 문장으로 보는 문제
  2. 왜 파이프라인 분기화가 올바른 패턴인가
  3. 접두사별 UseWhen (추천)
    • A1) Public /_api에만 미들웨어 실행
    • A2) Private /api에만 미들웨어 실행
    • A3) 확장 메서드로 Startup을 깔끔하게 유지
  4. 옵션 B – 엔드포인트 메타데이터 프로파일 (Enterprise Flex)
  5. 옵션 C – 라우트 그룹 (Minimal APIs)
  6. DI: “등록하지 않음” vs “실행하지 않음”
  7. 프로덕션 노트 (OData + Controllers + Ordering)
  8. 자주 발생하는 함정
  9. 최종 권고사항

한 문장으로 요약된 문제

/api/.../_api/...에 도달하는 요청에 대해 다른 미들웨어 체인이 필요하며, 각 미들웨어에 라우팅 로직을 섞 않고.

파이프라인 브랜칭이 올바른 패턴인 이유

혜택설명
관심사의 분리미들웨어는 “순수”하게 유지됩니다 – 라우트에 특화된 코드가 내부에 없습니다.
단일 진실 원천모든 라우트 기반 보안 정책이 한 곳(Startup 또는 확장)에서 관리됩니다.
성능브랜치와 일치하지 않는 요청은 해당 미들웨어를 인스턴스화하거나 실행하지 않습니다.
진화 가능성나중에 단순 프리픽스에서 보다 풍부한 엔드포인트 메타데이터로 쉽게 확장할 수 있습니다.

Prefix 로 UseWhen (추천)

계약에 이미 명확한 프리픽스(/_api/api)가 있는 경우 가장 깔끔한 솔루션입니다.

A1) Public /_api 전용 미들웨어 실행

// ------------------------------------------------------------
// 1️⃣  Place after UseRouting() and before endpoint mapping
// ------------------------------------------------------------
app.UseRouting();

// ✅ Branch: only /_api
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments(
        "/_api", StringComparison.OrdinalIgnoreCase),
    branch =>
    {
        branch.UseMiddleware();
        branch.UseMiddleware();
        branch.UseMiddleware();
        branch.UseMiddleware();
        branch.UseMiddleware();
    });

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

A2) Private /api 전용 미들웨어 실행

app.UseRouting();

// ✅ Branch: only /api
app.UseWhen(
    ctx => ctx.Request.Path.StartsWithSegments(
        "/api", StringComparison.OrdinalIgnoreCase),
    branch =>
    {
        branch.UseMiddleware();
        branch.UseMiddleware();
        branch.UseMiddleware();
        branch.UseMiddleware();
        branch.UseMiddleware();
    });

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

A3) Startup 를 깔끔하게 유지하는 확장 메서드

// ──────────────────────────────────────────────────────────────
//  Extension methods that encapsulate the branching logic
// ──────────────────────────────────────────────────────────────
public static class ApiSecurityPipelineExtensions
{
    public static IApplicationBuilder UsePublicApiSecurity(this IApplicationBuilder app)
    {
        return app.UseWhen(
            ctx => ctx.Request.Path.StartsWithSegments(
                "/_api", StringComparison.OrdinalIgnoreCase),
            branch =>
            {
                branch.UseMiddleware();
                branch.UseMiddleware();
                branch.UseMiddleware();
                branch.UseMiddleware();
                branch.UseMiddleware();
            });
    }

    public static IApplicationBuilder UsePrivateApiSecurity(this IApplicationBuilder app)
    {
        return app.UseWhen(
            ctx => ctx.Request.Path.StartsWithSegments(
                "/api", StringComparison.OrdinalIgnoreCase),
            branch =>
            {
                branch.UseMiddleware();
                branch.UseMiddleware();
                branch.UseMiddleware();
                branch.UseMiddleware();
                branch.UseMiddleware();
            });
    }
}

Configure 에서 사용하기

app.UseRouting();

app.UsePublicApiSecurity();   // 또는 app.UsePrivateApiSecurity();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

Option B — 엔드포인트‑메타데이터 프로파일 (Enterprise Flex)

예외가 필요할 때 예외(예: 일부 /_api 엔드포인트는 스택을 건너뛰어야 하거나, 일부 /api 엔드포인트는 옵트‑인해야 할 경우), 결정을 경로에서 메타데이터로 옮깁니다.

1️⃣ 사용자 정의 특성 정의

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = false)]
public sealed class SecurityMiddlewareProfileAttribute : Attribute
{
    public SecurityMiddlewareProfileAttribute(string profile) => Profile = profile;
    public string Profile { get; }
}

2️⃣ 프로파일 상수 중앙화

public static class SecurityProfiles
{
    public const string Public  = "public";
    public const string Private = "private";
    public const string None    = "none";   // skip all security middlewares
}

3️⃣ 컨트롤러 / 액션에 주석 달기

[SecurityMiddlewareProfile(SecurityProfiles.Public)]
[Route("_api/report/v1/[controller]")]
public class ReportController : ControllerBase
{
    // …
}

4️⃣ 엔드포인트 메타데이터를 기반으로 분기 (라우팅 후)

app.UseRouting();

app.UseWhen(ctx =>
{
    var endpoint = ctx.GetEndpoint();
    var profile  = endpoint?.Metadata
                            .GetMetadata<SecurityMiddlewareProfileAttribute>()
                            ?.Profile;

    // Execute the stack only for the "public" profile (adjust as needed)
    return string.Equals(profile, SecurityProfiles.Public,
                         StringComparison.OrdinalIgnoreCase);
},
branch =>
{
    branch.UseMiddleware();
    branch.UseMiddleware();
    branch.UseMiddleware();
    branch.UseMiddleware();
    branch.UseMiddleware();
});

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

이제 미들웨어 코드를 수정하지 않고도 엔드포인트별 동작을 세밀하게 조정할 수 있습니다.

옵션 C — 라우트 그룹 (Minimal API로 이동하는 경우)

var publicGroup = app.MapGroup("/_api")
    .AddEndpointFilter(async (context, next) =>
    {
        // optional per‑group filter logic
        return await next(context);
    });

publicGroup.UseMiddleware();
publicGroup.UseMiddleware();
publicGroup.UseMiddleware();
publicGroup.UseMiddleware();
publicGroup.UseMiddleware();

publicGroup.MapGet("/report/v1/{id}", (int id) => /* … */);

Minimal‑API 라우트 그룹은 더 유창한 구문으로 동일한 분기 기능을 제공합니다.

DI: “등록하지 않음” vs “실행하지 않음”

접근 방식사용 시점
등록하지 않음 미들웨어를 ConfigureServices미들웨어가 구성 비용이 많이 드는 경우이며 특정 배포에서는 절대 필요하지 않을 때.
실행하지 않음 (분기)미들웨어가 구성 비용이 적지만 요청의 일부에만 실행되어야 할 때.

대부분의 경우, 분기 (UseWhen)가 충분합니다. 미들웨어 파이프라인은 시작 시 한 번만 구축되며, 분기는 일치하지 않는 요청에 대해 실행을 건너뜁니다.

Production Notes (OData + Controllers + Ordering)

  1. UseWhenUseRouting 뒤에 배치 – 라우팅 이전에는 엔드포인트 메타데이터(OData 라우트 포함)가 사용할 수 없습니다.
  2. UseAuthentication / UseAuthorization보다 앞에 배치해야 하는 경우, 해당 미들웨어가 브랜치 외부에서 실행되어야 합니다(예: 비공개 엔드포인트에서도 인증을 유지하고 싶을 때).
    • 인증을 공용 브랜치에만 적용하고 싶다면, 인증 호출을 브랜치 내부로 이동하십시오.
  3. OData 라우트 등록 (app.UseEndpoints(endpoints => endpoints.MapODataRoute(...)))은 브랜치 후에 배치해야 브랜치가 최종 엔드포인트를 인식할 수 있습니다.
  4. 보안 미들웨어의 순서가 중요합니다 – 각 브랜치 내부에서는 선형 파이프라인에서와 동일한 순서를 유지하십시오.

일반적인 함정

함정증상해결책
UseRouting 앞에 브랜치를 배치ctx.GetEndpoint()null입니다; 접두사 확인은 동작할 수 있지만 이후 라우팅 기반 메타데이터는 작동하지 않습니다.브랜치를 app.UseRouting() 뒤에 이동하십시오.
StartsWithSegments 대신 StartsWith 사용/api2/.../api와 잘못 매칭됩니다.세그먼트 인식을 위해 StartsWithSegments를 사용하십시오.
필요할 때 브랜치 내부에서 app.UseAuthentication() 호출을 잊음브랜치 내부의 요청이 인증되지 않습니다.브랜치 내부에 인증 미들웨어를 추가하거나 모든 요청에 적용되어야 한다면 전역으로 유지하십시오.
같은 미들웨어를 두 번 등록 (전역과 브랜치 각각)중복 작업 및 잠재적 부작용.필요한 곳에만 등록하십시오.
UsePathBase 등 URL 재작성 후 HttpContext.Request.Path에 의존경로가 변경되어 불일치가 발생할 수 있습니다.ctx.Request.PathBasePath를 사용하거나, 프레디케이트를 적절히 조정하십시오.

최종 권고

  1. 프리픽스 기반 UseWhen 접근 방식부터 시작하세요 – 가장 간단하고 성능이 좋으며 미들웨어를 순수하게 유지합니다.
  2. 분기 로직을 확장 메서드에 캡슐화하여 Startup.Configure를 깔끔하게 유지합니다.
  3. 엔드포인트별 예외가 필요할 때는, 엔드포인트 메타데이터 프로필(옵션 B)로 전환합니다.
  4. Minimal API를 채택한다면, 라우트 그룹(옵션 C)을 고려하여 보다 유연한 경험을 얻으세요.

파이프라인을 분기함으로써 관심사의 명확한 분리를 달성하고, 불필요한 미들웨어 실행을 방지하며, API 범위가 확대됨에 따라 보안 모델을 발전시킬 수 있는 유연성을 유지합니다.

ASP.NET Core에서 엔드포인트별 미들웨어 제어

아래는 원본 토론을 정리한 버전으로, 동일한 구조와 내용을 유지합니다.

UseWhen을 이용한 분기

app.UseWhen(context => context.Request.Path.StartsWithSegments("/_api"), branch =>
{
    branch.UseMiddleware();
    branch.UseMiddleware();
    branch.UseMiddleware();
    branch.UseMiddleware();
    branch.UseMiddleware();
});

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

결과: 엔드포인트별 미들웨어 동작을 선언적으로 제어할 수 있습니다.

Minimal‑API 대안 – 라우트 그룹

Minimal API로 마이그레이션한다면 라우트 그룹을 사용해 “쉐프의 키스” 같은 문법을 사용할 수 있습니다:

var publicApi = app.MapGroup("/_api");
publicApi.UseMiddleware();
publicApi.UseMiddleware();

var privateApi = app.MapGroup("/api");
// privateApi.UseMiddleware();

참고: 컨트롤러는 라우트 그룹을 동일한 일급 방식으로 지원하지 않으므로, 현재로서는 UseWhen이 가장 안전한 접근법입니다.

“실행하지 않음” vs. “등록하지 않음”

당신이 말했습니다: “비공개일 때는 실행도 안 하고 의존성에도 등록하지 않길 원한다.”

ASP.NET Core에서는 실행을 방지하기 위해 미들웨어를 등록 해제할 필요가 없습니다:

상황동작
UseWhen(...) 내부에서 라우트 매치되지 않을 때• 미들웨어가 실행되지 않음.
• 일반적으로 해결되지 않음 (DI가 인스턴스를 생성하지 않음).
조건 분기 외부모든 요청에 대해 미들웨어가 실행됩니다.

따라서 실제 목표는 **“실행되지 않음”**이며, “등록하지 않음”은 보통 불필요한 복잡성을 추가합니다.

특정 배포 환경에서 등록 자체를 피하고 싶다면 환경‑별 구성으로 처리하세요 (예: 프로덕션에서만 일부 미들웨어를 등록).

순서가 중요합니다

UseRouting()은 라우트 메타데이터를 확인하는 어떤 분기보다 먼저 실행되어야 합니다.

배치 위치효과
UseRouting() 이후 (또는 그 이전) – 접두사 검사대부분의 시나리오에서 깔끔하게 동작합니다.
UseRouting() 이후엔드포인트 메타데이터 검사메타데이터는 이 시점에서만 사용할 수 있기 때문에 필요합니다.
엔드포인트 매핑(MapControllers(), MapHub() 등) 이후해당 분기는 요청에 영향을 주지 않음.
UseRouting() 이전메타데이터 검사GetEndpoint()null이 됩니다.

일반적인 파이프라인 레이아웃

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();               // ("/callshub");

흔히 발생하는 함정

  • UseWhen을 너무 늦게 배치 (엔드포인트 매핑 이후) → 절대 실행되지 않음.
  • UseRouting() 이전에 엔드포인트 메타데이터를 검사GetEndpoint()null.
  • 접두사 검사와 메타데이터 검사를 명확한 규칙 없이 혼용 → 미들웨어가 두 번 실행될 수 있음.
  • “공개/비공개”에 미들웨어 로직을 결합 → 미들웨어는 순수하게 유지하고, 언제 실행할지는 파이프라인이 결정하도록 합니다.

권장 사항

  1. 옵션 A – 접두사 기반 UseWhen부터 시작.
    현재 API 경계(/_api vs /api)에 맞으며 빠르게 적용할 수 있습니다.

  2. 엔드포인트별 세밀한 제어가 필요할 때옵션 B – 메타데이터 프로파일(예: 사용자 정의 엔드포인트 메타데이터 특성)로 확장합니다.

  3. 미들웨어는 조합 가능하고 순수하게 유지하고, “언제 실행할지”는 파이프라인 설정에 맡깁니다.

다음 단계

컨트롤러 라우팅 방식(특히 OData 라우트 포함)을 알려주시면, 정확한 UseWhen 블록과 OData 라우팅이나 UseEndpoints()와 충돌하지 않는 가장 안전한 순서를 생성해 드릴 수 있습니다.

행복한 배포 되세요! 🚀

Back to Blog

관련 글

더 보기 »

NuGet에서 WebForms Core

개요 WebForms Core는 Elanat에서 개발한 서버‑기반 UI 기술로, 이제 공식적으로 NuGet에서 패키지 이름 WFC로 제공됩니다. 이 패키지는…