인증과 인가의 개념 분리 이해
자주 혼동되는 인증 (Authentication) 과 인가 (Authorization) 는 소프트웨어 보안에서 명확히 구분되어야 하는 두 가지 독립적인 프로세스입니다. 인증은 사용자의 신원을 확인하는 단계라면, 인가는 확인된 사용자가 특정 리소스에 접근할 권한이 있는지 판단하는 과정입니다. ASP.NET Core 에서는 Identity 프레임워크가 사용자 멤버십 관리를 담당하지만, 실제 인가 로직이 실행되는 위치는 그 자체로 정의된 별도의 계층에 존재합니다. 이러한 구조적 특징을 이해하는 것은 유연한 권한 관리 시스템을 설계하는 데 필수적입니다.
기본 인가 속성의 동작 원리
구체적인 예제를 통해 인가 처리를 확인해보겠습니다. 기존 프로젝트에 컨트롤러를 추가하여 간단히 테스트 환경을 구축해 봅니다. 해당 컨트롤러의 액션 메서드에 [Authorize] 특성을 부여하면 시스템은 접근 제어 기능을 활성화합니다.
// SampleUsersController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace EnterpriseApp.Controllers
{
[ApiController]
[Route("api/v1/users")]
public class SampleUsersController : ControllerBase
{
[HttpGet]
[Authorize] // 로그인 상태 확인 요구
public ActionResult<UserInfoDto> GetProfile()
{
return Ok(new UserInfoDto
{
UserName = User.Identity?.Name,
IsAuthenticated = User.Identity?.IsAuthenticated ?? false
});
}
}
}
위 코드에서 사용자가 정상적으로 로그인을 마친 후 엔드포인트를 호출하면 현재 사용자 정보를 반환받습니다. 반대로 세션 없이 다시 요청을 보내면 인증 페이지로 리디렉션되거나 401 오류가 발생합니다. 여기서 핵심은 [Authorize] 특성 자체가 요청을 차단하지 않는다는 점입니다. 이 속성은 단순히 '인가 처리 필요'라는 신호를 전달하며, 실제로 접근을 차단하거나 통과시키는 역할은 MVC 파이프라인 내의 중간 처리기인 인가 필터가 수행합니다.
권한 처리가 발생하는 위치
기술적 깊이를 더하자면, 인증 과정은 Authentication Middleware 단계에서 완료되고, 이후 생성된 Principal 객체는 다음 파이프라인으로 전달됩니다. 이후에 도착한 MVC Middleware 내부에서는 컨트롤러와 액션 메서드를 실행하기 전에 AuthorizationFilter 가 작동합니다. 이 필터가 특성으로 정의된 규칙을 검사하고, 조건이 충족되지 않으면 요청을 중단시키는 것입니다.
단순히 [Authorize] 를 사용하는 경우 기본 동작은 인증 여부 (IsAuthenticated) 만 확인합니다. 하지만 기업용 애플리케이션에서는 역할 (Role) 이나 복잡한 비즈니스 규칙에 따른 접근 제어가 필요합니다.
고정 값 대신 정책을 사용하는 이유
역할 기반 접근 제어를 위해 속성 파라미터를 직접 지정하는 방법도 존재합니다.
[Authorize(Roles = "manager, admin")]
public async Task<IActionResult> ManageSettings() { }
이 방식은 코드가 직관적이지만, 역할 이름과 URL 이 하드코딩되어 결합되어 유지보수 측면에서 취약점을 가질 수 있습니다. 요구사항 변경 시 많은 코드를 수정해야 할 수도 있습니다. 이를 해결하기 위해 ASP.NET Core 는 정책 (Policy) 기반 인가를 제공합니다. 이를 통해 권한 검증 로직과 데이터를 분산시켜 관리할 수 있습니다.
커스텀 역할 정책 구현 단계
사용자가 'Manager' 또는 'Admin' 역할 중 하나를 가진 경우에만 접근 가능한 정책 'management-access'를 만드는 과정을 살펴보겠습니다.
1. 요구사항 클래스 정의
먼저 무엇을 검증할지 정의하는 클래스를 생성합니다. 이는 단순히 검증 대상 데이터만 포함하며 실제 로직은 없습니다.
// SecurityRequirements.cs
public class DepartmentAccessRequirement : IAuthorizationRequirement
{
public string[] RequiredRoles { get; }
public DepartmentAccessRequirement(params string[] roles)
{
RequiredRoles = roles ?? throw new ArgumentNullException(nameof(roles));
}
}
2. 처리자 (Handler) 구현
요구사항을 실제로 평가하는 핸들러를 작성합니다. 여기서는 주어진 역할 목록과 현재 컨텍스트의 사용자 역할을 비교합니다.
// SecurityHandlers.cs
public class DepartmentAccessHandler : AuthorizationHandler<DepartmentAccessRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DepartmentAccessRequirement requirement)
{
foreach (var role in requirement.RequiredRoles)
{
if (context.User.IsInRole(role))
{
// 권한 만족 시 처리 종료
context.Succeed(requirement);
return Task.CompletedTask;
}
}
// 모든 조건 실패 시
context.Fail();
return Task.CompletedTask;
}
}
3. 의존성 주입 및 정책 등록
작성한 구성 요소들을 서비스 컨테이너에 등록하고 정책 맵핑을 설정합니다.
// Startup.cs ConfigureServices
services.AddAuthorizationBuilder()
.AddPolicy("management-access", policy =>
{
policy.Requirements.Add(new DepartmentAccessRequirement("Manager", "Admin"));
})
.AddHandler<DepartmentAccessRequirement, DepartmentAccessHandler>();
// Singleton 으로 핸들러 등록 권장 사항에 따라 변경 가능
services.AddSingleton<IAuthorizationHandler, DepartmentAccessHandler>();
4. 컨트롤러 적용
마지막으로 이전에 사용한 하드코딩 방식을 정책 참조 방식으로 변경합니다.
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize(Policy = "management-access")]
public async Task<IActionResult> SubmitReport()
{
// 비즈니스 로직
return Ok();
}
특정 인증 스키마 지정
시스템 내에 쿠키 인증과 JWT 인증 같은 여러 인증 제공자가 공존하는 경우를 고려해야 합니다. 기본적으로는 메인 플로우에서 선택된 인증 결과가 사용되지만, [Authorize] 에서 특정 스키마명을 명시하여 재검증하게 할 수 있습니다.
[Authorize(AuthenticationSchemes = "BearerToken", Policy = "management-access")]
public IActionResult SecureEndpoint() { ... }
이때 발생하는 현상은 흥미롭습니다. 인증 매니저먼트 단계는 이미 끝났는데, MVC 단계에서 특정 스키마 검증을 요구할 때 어떻게 동작하나요? HTTP 컨텍스트에는 인증 결과를 다시 조회할 수 있는 확장 메서드인 AuthenticateAsync 가 준비되어 있습니다. 필터가 이 메서드를 사용하여 지정된 스키마로 한 번 더 인증 티켓을 발급받고, 새로운 ClaimsPrincipal 을 조합하여 권한 검사를 진행합니다.
인가 필터의 내부 흐름 분석
최종적으로 인가 결정이 내려지는 AuthorizationFilter 의 주요 절차는 다음과 같습니다.
- 재인증 단차: 정책에 특정 스키마가 명시되었다면
IAuthenticationService를 호출하여 해당 스키마의 유효성을 재확인하고 사용자 정보 (Principal) 를 통합합니다. 별도 지정이 없으면 기존 HttpContext.User 를 사용합니다. - 핸들러 체인 실행: 등록된
IAuthorizationHandler리스트 순서대로 요구사항 평가를 수행합니다. 각 핸들러는 승인 (Success) 이나 거부 (Fail) 상태를 컨텍스트에 기록합니다. - 결과 집계: 모든 핸들러 실행 후 최종 평가기를 통해 성공 여부를 판별하고, 성공 시 로그를 남긴 뒤 요청을 통과시킵니다. 일부 핸들러 실패 시 중단 플래그가 설정되어 있더라도 전체 결과에 영향을 줄 수 있도록 설정 가능합니다.