.NET Core 인증 및 권한 부여 메커니즘과 소스 코드 심층 분석

미들웨어 파이프라인과 권한 부여

.NET 환경에서 요청의 인증(Authentication)과 권한 부여(Authorization)를 처리하려면 반드시 전용 미들웨어를 파이프라인에 등록해야 합니다. 요청 처리 파이프라인 구성 단계에서 다음 두 미들웨어를 순서대로 추가하여 보안 검증을 활성화합니다.

미들웨어역할
UseAuthentication사용자의 신원을 확인하는 인증(Authentication) 처리
UseAuthorization인증된 사용자의 리소스 접근 권한을 검증하는 권한 부여(Authorization) 처리

1. 스킴(Scheme)과 역할(Role) 기반 접근 제어

쿠키 기반의 스킴을 사용하면 요청에 유효한 스킴이 존재하는지 확인하여 접근을 제어할 수 있습니다. 또한, 클레임(Claim)에 역할(Role) 정보를 담아 특정 역할을 가진 사용자만 접근하도록 제한할 수 있습니다. 현대적인 .NET 애플리케이션에서는 확장성과 유연성이 뛰어난 정책(Policy) 기반 방식을 주로 사용하며, 스킴과 역할 방식은 내부적으로 정책 기반으로 래핑되어 동작합니다.

서비스 등록 및 미들웨어 구성

// DI 컨테이너에 인증 서비스 등록
services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = "CustomCookieScheme";
    options.DefaultSignInScheme = "CustomCookieScheme";
})
.AddCookie("CustomCookieScheme", options =>
{
    options.LoginPath = "/Account/SignIn";
    options.AccessDeniedPath = "/Account/AccessDenied";
});

// 파이프라인에 미들웨어 추가 (반드시 인증 다음에 권한 부여가 와야 함)
app.UseAuthentication();
app.UseAuthorization();

로그인 및 역할 기반 클레임 발급

[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> SignIn(string userId, string userPwd)
{
    // 간단한 자격 증명 검증
    if (userId != "manager01" || userPwd != "securePass!")
    {
        return Json(new { Success = false, Message = "잘못된 자격 증명" });
    }

    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, userId),
        new Claim(ClaimTypes.Role, "Manager")
    };
    
    var identity = new ClaimsIdentity(claims, "CustomCookieScheme");
    var principal = new ClaimsPrincipal(identity);

    await HttpContext.SignInAsync("CustomCookieScheme", principal, new AuthenticationProperties
    {
        ExpiresUtc = DateTimeOffset.UtcNow.AddHours(2)
    });

    return Json(new { Success = true, Message = "환영합니다." });
}

컨트롤러 액션에 역할 제한 적용

[Authorize(Roles = "Manager")]
public IActionResult Dashboard()
{
    return View();
}

2. 정책(Policy) 기반 접근 제어

정책 기반 방식은 복잡한 비즈니스 규칙을 선언적으로 적용할 수 있게 해줍니다. 예를 들어, 특정 역할을 가지면서 동시에 특정 클레임 값을 만족해야 하는 조건을 쉽게 조합할 수 있습니다.

정책 정의 및 등록

services.AddAuthorization(options =>
{
    // 관리자 정책: Manager 역할이면서 Department 클레임이 'IT'여야 함
    options.AddPolicy("ITManagementPolicy", policy =>
    {
        policy.RequireRole("Manager");
        policy.RequireClaim("Department", "IT");
    });

    // 일반 직원 정책: 특정 클레임 존재 여부 및 커스텀 조건 검증
    options.AddPolicy("RegularStaffPolicy", policy =>
    {
        policy.RequireAssertion(context =>
            context.User.HasClaim(c => c.Type == "EmployeeLevel") &&
            int.TryParse(context.User.FindFirst("EmployeeLevel")?.Value, out int level) &&
            level >= 3
        );
    });
});

정책을 액션에 적용

참고로 [AllowAnonymous] 특성이 존재하면 [Authorize] 특성은 무시됩니다.

[Authorize(Policy = "ITManagementPolicy")]
public IActionResult ITAdminPanel()
{
    return View();
}

내부 동작 원리 및 소스 코드 분석

위와 같은 설정이 실제로 어떻게 동작하는지 이해하려면 프레임워크 내부의 파이프라인과 소스 코드를 살펴볼 필요가 있습니다. 권한 부여 프로세스는 크게 '규칙 등록 단계'와 '요청 검증 단계'로 나뉩니다.

1. 규칙 등록 파이프라인 분석

권한 부여 설정은 주로 다음 핵심 클래스들을 통해 이루어집니다.

클래스설명
AuthorizationOptions정의된 정책(Policy)들을 딕셔너리 형태로 저장하고 관리하는 설정 클래스
AuthorizationPolicyBuilder정책에 요구 사항(Requirements)을 추가하고 AuthorizationPolicy 객체를 생성하는 빌더
IAuthorizationRequirement권한 부여 조건을 나타내는 마커 인터페이스. 실제 검증 로직은 핸들러에서 수행

AddAuthorization 메서드에 전달되는 델리게이트는 AuthorizationOptions를 구성합니다. 내부적으로 AddPolicy 메서드는 AuthorizationPolicyBuilder를 사용하여 요구 사항들을 수집하고, 이를 불변의 AuthorizationPolicy 객체로 변환하여 PolicyMap에 캐싱합니다.

2. 커스텀 요구 사항(Requirement) 확장

IAuthorizationRequirement를 구현하고 AuthorizationHandler를 상속받으면 맞춤형 검증 로직을 만들 수 있습니다. 예를 들어, 사원 번호가 특정 접두사로 시작하는지 확인하는 정책을 만들어 보겠습니다.

커스텀 핸들러 구현

public class EmployeePrefixRequirement : IAuthorizationRequirement
{
    public string RequiredPrefix { get; }
    public EmployeePrefixRequirement(string prefix) => RequiredPrefix = prefix;
}

public class EmployeePrefixHandler : AuthorizationHandler<EmployeePrefixRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EmployeePrefixRequirement requirement)
    {
        var employeeIdClaim = context.User.FindFirst("EmployeeId");
        
        if (employeeIdClaim != null && employeeIdClaim.Value.StartsWith(requirement.RequiredPrefix, StringComparison.OrdinalIgnoreCase))
        {
            context.Succeed(requirement);
        }
        
        return Task.CompletedTask;
    }
}

서비스 등록 및 사용

// 핸들러를 DI 컨테이너에 등록
services.AddSingleton<IAuthorizationHandler, EmployeePrefixHandler>();

// 정책 정의
services.AddAuthorization(options =>
{
    options.AddPolicy("SeniorStaffOnly", policy =>
        policy.Requirements.Add(new EmployeePrefixRequirement("SNR-")));
});

// 컨트롤러 적용
[Authorize(Policy = "SeniorStaffOnly")]
public IActionResult SeniorDashboard()
{
    return View();
}

3. 요청 검증(실행) 파이프라인 분석

클라이언트의 요청이 들어오면 AuthorizationMiddleware가 이를 가로채어 권한 부여 프로세스를 시작합니다. 주요 흐름은 다음과 같습니다.

  1. 메타데이터 추출: 현재 엔드포인트(컨트롤러 액션 등)에서 IAuthorizeData 인터페이스를 구현한 특성([Authorize])을 찾습니다.
  2. 정책 결합: 추출한 메타데이터를 기반으로 적용해야 할 AuthorizationPolicy를 조합합니다.
  3. 인증 평가: IPolicyEvaluatorAuthenticateAsync를 호출하여 현재 사용자의 인증 상태를 확인합니다.
  4. 익명 허용 확인: 엔드포인트에 [AllowAnonymous]이 있는지 확인하여 있다면 검증 과정을 건너뜁니다.
  5. 권한 평가: IPolicyEvaluatorAuthorizeAsync를 호출하며, 이는 내부적으로 IAuthorizationService로 위임됩니다.
  6. 핸들러 실행: DefaultAuthorizationService는 등록된 모든 IAuthorizationHandler를 실행하여 컨텍스트의 성공(Succeed) 또는 실패(Fail) 상태를 결정합니다.

이러한 미들웨어 기반의 설계는 각 컴포넌트의 책임을 명확히 분리하면서도, 개발자가 핸들러나 정책 제공자(Policy Provider)를 교체하여 프레임워크의 기본 동작을 쉽게 확장할 수 있도록 해줍니다.

태그: ASP.NET Core authorization middleware C# Security

6월 27일 04:52에 게시됨