ASP.NET Core MVC의 클레임 기반 권한 부여

앱에 로그인할 때 앱 사용자에 대한 ID가 만들어지면 ID 공급자가 사용자 ID에 하나 이상의 클레임 을 할당할 수 있습니다. 클레임은 주체(사용자, 앱 또는 서비스, 디바이스/컴퓨터)가 수행할 수 있는 작업이 아니라, 주체가 무엇인지를 나타내는 이름과 값의 쌍입니다. 권한 부여 프로세스 중에 앱에서 클레임을 평가하여 데이터 및 기타 보안 리소스에 대한 액세스 권한을 결정할 수 있으며, 주체에 대한 인증 결정을 내리거나 표현하는 데 사용할 수도 있습니다. ID는 여러 값을 가진 여러 클레임을 포함할 수 있으며 동일한 형식의 여러 클레임을 포함할 수 있습니다. 이 문서에서는 ASP.NET Core 앱에서 권한 부여에 대한 클레임 검사를 추가하는 방법을 설명합니다.

이 문서에서는 MVC 예제를 사용하고 MVC 시나리오에 중점을 둡니다. Blazor 및 Razor 페이지 지침은 다음 리소스를 참조하세요.

  • ASP.NET Core
  • ASP.NET Core Pages에서의 클레임 기반 권한 부여

샘플 앱

이 문서의 샘플 앱은 WebAll 샘플 앱(dotnet/AspNetCore.Docs.Samples GitHub 리포지토리)( 다운로드 방법)입니다. 자세한 내용은 샘플의 추가 정보 파일(README.md)을 참조하세요.

클레임 검사 추가

클레임 기반 권한 부여 검사는 선언적이며 컨트롤러 내의 컨트롤러 또는 작업에 적용됩니다.

코드의 클레임은 현재 사용자가 소유해야 하는 클레임을 지정하고, 필요에 따라 요청된 리소스에 액세스하기 위해 클레임이 보유해야 하는 값을 지정합니다. 클레임 요구 사항은 정책 기반입니다. 개발자는 클레임 요구 사항을 나타내는 정책을 빌드하고 등록해야 합니다.

가장 간단한 유형의 클레임 정책은 클레임의 존재를 찾고 값을 확인하지 않습니다.

정책을 빌드하고 등록한 후 UseAuthorization을 호출하십시오 (UseAuthentication을 호출하는 줄 뒤에 호출을 배치). 정책 등록은 일반적으로 Program 파일 내에서 권한 부여 서비스 설정의 일부로 수행됩니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

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

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

정책을 빌드하고 등록한 후, UseAuthentication 호출 라인 뒤에 UseAuthorization을(를) 호출합니다. 정책 등록은 일반적으로 Program 파일 내에서 권한 부여 서비스 설정의 일부로 수행됩니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorization(options =>
{
   options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

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

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();

권한 부여 서비스의 구성에서 Startup.ConfigureServices 정책을Startup.cs 빌드하고 등록합니다.

services.AddAuthorization(options =>
{
    options.AddPolicy("EmployeeOnly", 
        policy => policy.RequireClaim("EmployeeNumber"));
});

UseAuthentication가 호출된 직후 UseAuthorizationStartup.Configure (Startup.cs)에서 호출하십시오.

app.UseAuthorization();

속성 Policy[Authorize] 속성을 적용하여 정책 이름을 지정합니다. 다음 EmployeeOnly 예제에서 정책은 현재 ID에 EmployeeNumber 클레임이 있는지 확인합니다.

[Authorize(Policy = "EmployeeOnly")]
public IActionResult VacationBalance()
{
    return View();
}

특성은 [Authorize] 전체 컨트롤러에 적용할 수 있으며, 이 경우 정책과 일치하는 ID만 컨트롤러의 모든 작업에 액세스할 수 있습니다.

[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public ActionResult VacationBalance()
    {
        return View();
    }

    [AllowAnonymous]
    public ActionResult VacationPolicy()
    {
        return View();
    }
}

[Authorize] 속성으로 보호된 컨트롤러가 있는 경우, 하지만 특정 작업에 대한 익명 액세스를 허용하려는 경우 [AllowAnonymous] 속성을 적용합니다.

[Authorize(Policy = "EmployeeOnly")]
public class VacationController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public ActionResult VacationBalance()
    {
        return View();
    }

    [AllowAnonymous]
    public ActionResult VacationPolicy()
    {
        return View();
    }
}

정책을 만들 때 허용되는 값 목록을 지정할 수 있습니다. 다음 정책은 직원 번호가 1, 2, 3, 4 또는 5인 직원에게만 유효합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("Founders", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

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

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Founders", policy =>
                      policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

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

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

app.MapDefaultControllerRoute();
app.MapRazorPages();

app.Run();
services.AddAuthorization(options =>
{
    options.AddPolicy("Founder", policy =>
        policy.RequireClaim("EmployeeNumber", "1", "2", "3", "4", "5"));
});

일반 클레임 검사 추가

클레임 값이 단일 값이 아니거나 패턴 일치, 클레임 발급자 확인 또는 복잡한 클레임 값 구문 분석과 같은 보다 유연한 클레임 평가 논리가 필요한 경우 사용합니다 RequireAssertionHasClaim. 예를 들어 다음 정책을 사용하려면 사용자의 email 클레임이 특정 도메인으로 끝나야 합니다.

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});
services.AddAuthorization(options =>
{
    options.AddPolicy("ContosoOnly", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                c.Type == "email" &&
                c.Value.EndsWith("@contoso.com", StringComparison.OrdinalIgnoreCase))));
});

자세한 내용은 ASP.NET Core 참조하세요.

여러 정책 평가

컨트롤러 및 작업 수준에서 여러 정책을 적용하는 경우 액세스 권한이 부여되기 전에 모든 정책을 통과해야 합니다.

[Authorize(Policy = "EmployeeOnly")]
public class SalaryController : Controller
{
    public IActionResult Index()
    {
        return View();
    }

    public IActionResult Payslip()
    {
        return View();
    }

    [Authorize(Policy = "HumanResources")]
    public IActionResult UpdateSalary()
    {
        return View();
    }
}

앞의 예제에서 정책을 충족하는 EmployeeOnly 모든 ID는 그 정책이 컨트롤러에서 시행되기 때문에 Payslip 작업에 액세스할 수 있습니다. UpdateSalary 작업을 호출하려면, ID가 EmployeeOnly 정책과 HumanResources 정책을 모두 충족해야 합니다.

생년월일 클레임을 수락하고, 나이를 계산한 다음, 나이가 21세 이상인지 확인하는 것과 같은 더 복잡한 정책을 원하는 경우에는 사용자 지정 정책 처리기를 작성해야 합니다.

클레임 대/소문자 구분

클레임 StringComparison.Ordinal을 사용하여 비교됩니다. 즉 Admin ( 대문자 A) 및 admin (소문자 a)는 ID를 만든 인증 처리기에 관계없이 항상 다른 클레임 값으로 처리됩니다.

별도로 클레임 형식 비교(예: email형식별로 클레임을 찾는 데 사용됨)는 구현에 ClaimsIdentity 따라 대/소문자를 구분하거나 대/소문자를 구분하지 않을 수 있습니다. ASP.NET Core 8.0 이상에서 Microsoft.IdentityModel, AddJwtBearer, AddOpenIdConnect, AddWsFederation, AddMicrosoftIdentityWebApp, /, AddMicrosoftIdentityWebApi를 사용할 때, 대/소문자 구분 클레임 형식 일치를 사용하는 토큰 유효성 검사 중에 CaseSensitiveClaimsIdentity가 생성됩니다.

.NET 런타임에서 제공하는 기본 ClaimsIdentity(대부분의 경우 모든 cookie 기반 흐름 포함)은 대/소문자를 구분하지 않는 클레임 형식 일치를 사용합니다.

실제로 이 구분은 ID를 만드는 동안 클레임 유형이 한 번 구성되고 일관되게 일치하는 경우는 거의 중요하지 않습니다. 이는 클레임으로 표시되는 역할에도 적용됩니다. 미묘한 문제를 방지하려면 항상 클레임 값 및 클레임 유형에 일관된 대/소문자 구분을 사용합니다.