ASP.NET Core의 정책 기반 권한 부여

역할 기반 권한 부여클레임 기반 권한 부여는 요구 사항, 요구 사항 처리기 및 미리 구성된 정책을 사용합니다. 이러한 구성 요소에서는 코드에서 권한 부여 평가의 식을 지원합니다. 그 결과 더 풍부하고 재사용이 가능하며 테스트가 가능한 권한 부여 구조가 생성됩니다.

권한 부여 정책은 하나 이상의 요구 사항으로 구성됩니다. 앱 파일에서 권한 부여 서비스 구성의 Program.cs 일부로 등록합니다.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

앞의 예제에서 “AtLeast21” 정책이 생성되었습니다. 여기에는 최소 연령이라는 하나의 요구 사항이 있으며, 이는 요구 사항에 대한 매개 변수로서 제공됩니다.

IAuthorizationService

권한 부여가 성공했는지 확인하는 기본 서비스는 IAuthorizationService입니다.

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

위의 코드는 IAuthorizationService의 두 메서드를 강조 표시합니다.

IAuthorizationRequirement는 메서드가 없는 표식 서비스이며 권한 부여가 성공했는지 여부를 추적하기 위한 메커니즘입니다.

IAuthorizationHandler는 요구 사항이 충족되는지 확인하는 역할을 합니다.

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

AuthorizationHandlerContext 클래스는 처리기가 요구 사항이 충족되었는지 여부를 표시하는 데 사용하는 것입니다.

 context.Succeed(requirement)

다음 코드는 권한 부여 서비스의 간소화된(그리고 주석으로 주석이 추가된) 기본 구현을 보여 줍니다.

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

다음 코드는 일반적인 권한 부여 서비스 구성을 보여 줍니다.

// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, MyHandler1>();
// MyHandler2, ...

builder.Services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

// Configure your policies
builder.Services.AddAuthorization(options =>
      options.AddPolicy("Something",
      policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));

권한 부여를 위해 IAuthorizationService, [Authorize(Policy = "Something")] 또는 RequireAuthorization("Something")을 사용합니다.

MVC 컨트롤러에 정책 적용

Razor Pages를 사용하는 앱의 경우 Razor Pages에 정책 적용 섹션을 참조하세요.

정책 이름에 특성을 사용하여 컨트롤러에 [Authorize] 정책을 적용합니다.

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

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

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Controller : Controller
{
    public IActionResult Index() => View();
}

Razor Pages에 정책 적용

정책 이름과 함께 [Authorize] 특성을 사용하여 Razor Pages에 정책을 적용합니다. 예시:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace AuthorizationPoliciesSample.Pages;

[Authorize(Policy = "AtLeast21")]
public class AtLeast21Model : PageModel { }

정책은 Razor 페이지 처리기 수준에서 적용될 수 없고, 페이지에 적용하여야 합니다.

정책은 또한 권한 부여 규칙을 사용하여 Razor Pages에 적용할 수도 있습니다.

엔드포인트에 정책 적용

정책 이름과 함께 RequireAuthorization을 사용하여 엔드포인트에 정책을 적용합니다. 예시:

app.MapGet("/helloworld", () => "Hello World!")
    .RequireAuthorization("AtLeast21");

요구 사항

권한 부여 요구 사항은 정책이 현재 사용자 주체를 평가하는 데 사용할 수 있는 데이터 매개 변수의 컬렉션입니다. “AtLeast21” 정책에서는 최소 연령이라는 하나의 요구 사항이 있습니다. 요구 사항은 빈 표식 인터페이스인 구현합니다 IAuthorizationRequirement. 매개 변수가 있는 최소 연령 요구 사항은 다음과 같이 구현할 수 있습니다.

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minimumAge) =>
        MinimumAge = minimumAge;

    public int MinimumAge { get; }
}

권한 부여 정책에 여러 권한 부여 요구 사항이 포함된 경우 정책 평가가 성공하려면 모든 요구 사항이 통과되어야 합니다. 즉, 단일 권한 부여 정책에 추가된 여러 권한 부여 요구 사항은 AND 기준으로 처리됩니다.

참고 항목

요구 사항에는 데이터 또는 속성이 필요하지 않습니다.

권한 부여 처리기

권한 부여 처리기는 요구 사항의 속성에 대한 평가를 담당합니다. 권한 부여 처리기는 제공된 AuthorizationHandlerContext 요구 사항을 평가하여 액세스가 허용되는지 확인합니다.

요구 사항에는 여러 처리기가 있을 수 있습니다. 처리기에서는 AuthorizationHandler<TRequirement>를 상속할 수 있으며, TRequirement은 처리 대상이 되는 요구 사항입니다. 또는 처리기가 둘 이상의 요구 사항을 처리하기 위해 직접 구현 IAuthorizationHandler 할 수 있습니다.

하나의 요구 사항에 처리기 사용

다음 예제에서는 최소 연령 처리기가 단일 요구 사항을 처리하는 일 대 일 관계를 보여 줍니다.

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = context.User.FindFirst(
            c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com");

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

위의 코드는 현재 사용자 주체에 알려진 신뢰할 수 있는 발급자가 발급한 생년월일 클레임이 있는지 확인합니다. 클레임이 누락된 경우 권한 부여가 발생할 수 없으며, 이 경우 완료된 작업이 반환됩니다. 클레임이 있으면 사용자의 연령이 계산됩니다. 사용자가 요구 사항에 정의된 최소 연령을 충족하는 경우 권한 부여가 성공한 것으로 간주됩니다. 권한 부여가 성공하면 context.Succeed는 충족된 요구 사항을 유일한 매개 변수로 호출합니다.

여러 요구 사항에 처리기 사용

다음 예제에서는 권한 처리기가 세 가지 유형의 요구 사항을 처리할 수 있는 일 대 다 관계를 보여 줍니다.

using System.Security.Claims;
using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission || requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}

이전 코드에서는 성공적으로 표시되지 아니한 요구 사항을 포함하는 속성인 PendingRequirements을 트래버스합니다. ReadPermission 요구 사항의 경우 요청된 리소스에 액세스하려면 사용자가 소유자 또는 스폰서여야 합니다. EditPermission 또는 DeletePermission 요구 사항의 경우 요청된 리소스에 액세스하려면 소유자여야 합니다.

처리기 등록

구성 중에 서비스 컬렉션에 처리기를 등록합니다. 예시:

builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

위의 코드에서는 MinimumAgeHandler를 싱글톤으로 등록합니다. 기본 제공 서비스 수명을 사용하여 처리기를 등록할 수 있습니다.

요구 사항과 처리기를 IAuthorizationRequirementIAuthorizationHandler 모두를 구현하는 단일 클래스로 번들로 묶을 수 있습니다. 이렇게 하면 처리기와 요구 사항이 긴밀하게 결합됩니다. 이는 간단한 요구 사항 및 처리기에만 권장됩니다. 두 인터페이스를 모두 구현하는 클래스를 만들면 요구 사항을 직접 처리할 수 있는 기본 제공 PassThroughAuthorizationHandler로 인해 DI에 처리기를 등록할 필요가 없습니다.

AssertionRequirement가 완전히 자체 포함된 클래스의 요구 사항 및 처리기인 좋은 예제는 AssertionRequirement 클래스를 참조하세요.

처리기는 무엇을 반환해야 합니까?

처리기 예제Handle 메서드는 값을 반환하지 않습니다. 성공 또는 실패 상태는 어떻게 표시하나요?

  • 처리기는 context.Succeed(IAuthorizationRequirement requirement)를 호출하여 성공적으로 유효성이 검사된 요구 사항을 전달함으로써 성공을 나타냅니다.

  • 동일한 요구 사항에 대한 다른 처리기가 성공할 수 있기 때문에 처리기는 일반적으로 오류를 처리할 필요가 없습니다.

  • 다른 요구 사항 처리기가 성공하더라도 실패를 보장하려면 context.Fail을 호출합니다.

처리기에서 context.Succeed 또는 context.Fail을 호출하는 경우 다른 모든 처리기가 계속 호출됩니다. 이렇게 하면 다른 처리기가 요구 사항의 유효성을 성공적으로 검사하거나 실패한 경우에도 로깅과 같은 부작용이 발생할 수 있습니다. 로 false설정하면 속성이 InvokeHandlersAfterFailure 호출되면 처리기 실행을 단락합니다 context.Fail . InvokeHandlersAfterFailure는 기본적으로 true로 설정되며, 이 경우 모든 처리기가 호출됩니다.

참고 항목

인증이 실패하더라도 권한 부여 처리기가 호출됩니다. 처리기는 어떤 순서로든 실행할 수 있으므로 특정 순서로 호출되는 처리기에 의존하지 마세요.

요구 사항에 대해 여러 처리기를 원하는 이유는 무엇인가요?

OR 기준으로 평가하려는 경우 단일 요구 사항에 대해 여러 처리기를 구현합니다. 예를 들어 Microsoft에는 키 카드로만 열리는 문이 있습니다. 키 카드를 집에 그대로 두면 수신자가 임시 스티커를 인쇄하고 문을 엽니다. 이 시나리오에서는 BuildingEntry라는 단일 요구 사항이 있지만 각 요구 사항을 검사하는 여러 처리기가 있습니다.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "BadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using AuthorizationPoliciesSample.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;

namespace AuthorizationPoliciesSample.Policies.Handlers;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(
            c => c.Type == "TemporaryBadgeId" && c.Issuer == "https://microsoftsecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

두 처리기가 모두 등록되었는지 확인합니다. 정책이 BuildingEntryRequirement를 평가할 때 두 처리기가 성공하면 정책 평가가 성공합니다.

함수를 사용하여 정책 수행

정책을 이행하는 것이 코드로 표현하기 쉬운 상황이 있을 수 있습니다. RequireAssertion 정책 작성기를 사용하여 정책을 구성할 때 Func<AuthorizationHandlerContext, bool>을 제공할 수 있습니다.

예를 들어 이전 BadgeEntryHandler를 다음과 같이 다시 작성할 수 있습니다.

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context => context.User.HasClaim(c =>
            (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
            && c.Issuer == "https://microsoftsecurity")));
});

처리기에서 MVC 요청 컨텍스트에 액세스

HandleRequirementAsync 메서드에는 처리 중인 AuthorizationHandlerContextTRequirement라는 두 매개 변수가 있습니다. MVC 또는 SignalR과 같은 프레임워크는 추가 정보를 전달하기 위해 개체를 AuthorizationHandlerContextResource 속성에 자유롭게 추가할 수 있습니다.

엔드포인트 라우팅을 사용하는 경우 권한 부여는 일반적으로 권한 부여 미들웨어에 의해 처리됩니다. 이 경우 Resource 속성은 HttpContext의 인스턴스입니다. 컨텍스트를 사용하여 현재 엔드포인트에 액세스할 수 있습니다. 이 엔드포인트는 라우팅하는 기본 리소스를 프로브하는 데 사용할 수 있습니다. 예시:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

기존 라우팅을 사용하거나 MVC 권한 부여 필터의 일부로 권한 부여가 발생하는 경우 Resource의 값은 AuthorizationFilterContext 인스턴스입니다. 이 속성은 MVC 및 Razor Pages에서 제공되는 HttpContext, RouteData 및 모든 항목에 대한 액세스를 제공합니다.

Resource 속성을 사용하는 것은 프레임워크와 관련이 있습니다. Resource 속성의 정보를 사용하면 권한 부여 정책이 특정 프레임워크로 제한됩니다. is 키워드를 사용하여 Resource 속성을 캐스팅한 다음, 다른 프레임워크에서 실행될 때 코드가 InvalidCastException과 충돌하지 않도록 캐스팅이 성공했는지 확인해야 합니다.

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

전역적으로 모든 사용자를 인증하도록 요구

모든 사용자의 인증을 전역으로 요구하는 방법에 대한 자세한 내용은 인증된 사용자 요구를 참조하세요.

외부 서비스를 사용한 권한 부여 샘플

AspNetCore.Docs.Samples의 샘플 코드는 외부 권한 부여 서비스를 사용하여 추가 권한 부여 요구 사항을 구현하는 방법을 보여 줍니다. 샘플 Contoso.API 프로젝트는 Azure AD 사용하여 보호됩니다. 프로젝트의 추가 권한 부여 검사는 클라이언트 앱이 Contoso.Security.API API를 호출할 수 있는지 여부를 Contoso.API 설명하는 페이로드를 GetWeather 반환합니다.

샘플 구성

  1. Azure Active Directory(Azure AD) 테넌트에서 애플리케이션 등록을 만듭니다.
  • AppRole을 할당합니다.
  • API 권한에서 AppRole을 권한으로 추가하고 관리 동의를 부여합니다. 이 설정에서 이 앱 등록은 API를 호출하는 API와 클라이언트를 모두 나타냅니다. 원하는 경우 두 개의 앱 등록을 만들 수 있습니다. 이 설정을 사용하는 경우 API 권한만 수행하고 AppRole을 클라이언트에 대한 권한 단계로 추가합니다. 클라이언트 앱 등록에서만 클라이언트 암호 생성을 요합니다.
  1. 다음 설정을 사용하여 Contoso.API 프로젝트를 구성합니다.
{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "<Tenant name from AAD properties>.onmicrosoft.com">,
    "TenantId": "<Tenant Id from AAD properties>",
    "ClientId": "<Client Id from App Registration representing the API>"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
  1. 다음 설정을 사용하여 구성 Contoso.Security.API 합니다.
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "<Use the appropriate Client Id representing the Client calling the API>"
  ]
}
  1. ContosoAPI.postman_collection.json 파일을 Postman으로 가져온 이후 다음과 같이 환경을 설정합니다.

    • ClientId: API를 호출하는 클라이언트를 나타내는 앱 등록의 클라이언트 ID입니다.
    • clientSecret: API를 호출하는 클라이언트를 나타내는 앱 등록의 클라이언트 암호입니다.
    • TenantId: AAD 속성의 테넌트 ID입니다.
  2. 솔루션을 실행하고 Postman을 사용하여 API를 호출합니다. Contoso.Security.API.SecurityPolicyController에 중단점을 추가하고 날씨 가져오기가 허용되는지 여부를 어설션하는 데 사용되는 클라이언트 ID가 전달되는 것을 관찰할 수 있습니다.

추가 리소스

역할 기반 권한 부여클레임 기반 권한 부여는 요구 사항, 요구 사항 처리기 및 미리 구성된 정책을 사용합니다. 이러한 구성 요소에서는 코드에서 권한 부여 평가의 식을 지원합니다. 그 결과 더 풍부하고 재사용이 가능하며 테스트가 가능한 권한 부여 구조가 생성됩니다.

권한 부여 정책은 하나 이상의 요구 사항으로 구성됩니다. 정책은 Startup.ConfigureServices 메서드에 권한 부여 서비스 구성의 일부로 등록됩니다.

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });
}

앞의 예제에서 “AtLeast21” 정책이 생성되었습니다. 여기에는 최소 연령이라는 하나의 요구 사항이 있으며, 이는 요구 사항에 대한 매개 변수로서 제공됩니다.

IAuthorizationService

권한 부여가 성공했는지 확인하는 기본 서비스는 IAuthorizationService입니다.

/// <summary>
/// Checks policy based permissions for a user
/// </summary>
public interface IAuthorizationService
{
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check 
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, 
                                     IEnumerable<IAuthorizationRequirement> requirements);

    /// <summary>
    /// Checks if a user meets a specific authorization policy
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">
    /// An optional resource the policy should be checked with.
    /// If a resource is not required for policy evaluation you may pass null as the value
    /// </param>
    /// <param name="policyName">The name of the policy to check against a specific 
    /// context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// Returns a flag indicating whether the user, and optional resource has fulfilled 
    /// the policy.    
    /// <value>true</value> when the policy has been fulfilled; 
    /// otherwise <value>false</value>.
    /// </returns>
    /// <remarks>
    /// Resource is an optional parameter and may be null. Please ensure that you check
    /// it is not null before acting upon it.
    /// </remarks>
    Task<AuthorizationResult> AuthorizeAsync(
                                ClaimsPrincipal user, object resource, string policyName);
}

위의 코드는 IAuthorizationService의 두 메서드를 강조 표시합니다.

IAuthorizationRequirement는 메서드가 없는 표식 서비스이며 권한 부여가 성공했는지 여부를 추적하기 위한 메커니즘입니다.

IAuthorizationHandler는 요구 사항이 충족되는지 확인하는 역할을 합니다.

/// <summary>
/// Classes implementing this interface are able to make a decision if authorization
/// is allowed.
/// </summary>
public interface IAuthorizationHandler
{
    /// <summary>
    /// Makes a decision if authorization is allowed.
    /// </summary>
    /// <param name="context">The authorization information.</param>
    Task HandleAsync(AuthorizationHandlerContext context);
}

AuthorizationHandlerContext 클래스는 처리기가 요구 사항이 충족되었는지 여부를 표시하는 데 사용하는 것입니다.

 context.Succeed(requirement)

다음 코드는 권한 부여 서비스의 간소화된(그리고 주석으로 주석이 추가된) 기본 구현을 보여 줍니다.

public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, 
             object resource, IEnumerable<IAuthorizationRequirement> requirements)
{
    // Create a tracking context from the authorization inputs.
    var authContext = _contextFactory.CreateContext(requirements, user, resource);

    // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
    var handlers = await _handlers.GetHandlersAsync(authContext);

    // Invoke all handlers.
    foreach (var handler in handlers)
    {
        await handler.HandleAsync(authContext);
    }

    // Check the context, by default success is when all requirements have been met.
    return _evaluator.Evaluate(authContext);
}

다음 코드는 일반적인 ConfigureServices를 보여 줍니다.

public void ConfigureServices(IServiceCollection services)
{
    // Add all of your handlers to DI.
    services.AddSingleton<IAuthorizationHandler, MyHandler1>();
    // MyHandler2, ...

    services.AddSingleton<IAuthorizationHandler, MyHandlerN>();

    // Configure your policies
    services.AddAuthorization(options =>
          options.AddPolicy("Something",
          policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));


    services.AddControllersWithViews();
    services.AddRazorPages();
}

권한 부여를 위해 IAuthorizationService 또는 [Authorize(Policy = "Something")]을 사용합니다.

MVC 컨트롤러에 정책 적용

Razor Pages를 사용하는 경우 이 문서의 Razor Pages에 정책 적용을 참조하세요.

정책은 정책 이름의 특성을 사용하여 [Authorize] 컨트롤러에 적용됩니다. 예시:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
    public IActionResult Index() => View();
}

Razor Pages에 정책 적용

정책 이름과 함께 [Authorize] 특성을 사용하여 Razor Pages에 정책을 적용합니다. 예시:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;

[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

정책은 Razor 페이지 처리기 수준에서 적용될 수 없고, 페이지에 적용하여야 합니다.

정책은 권한 부여 규칙을 사용하여 Razor Pages에 적용할 수도 있습니다.

요구 사항

권한 부여 요구 사항은 정책이 현재 사용자 주체를 평가하는 데 사용할 수 있는 데이터 매개 변수의 컬렉션입니다. “AtLeast21” 정책에서는 최소 연령이라는 하나의 요구 사항이 있습니다. 요구 사항은 빈 표식 인터페이스인 구현합니다 IAuthorizationRequirement. 매개 변수가 있는 최소 연령 요구 사항은 다음과 같이 구현할 수 있습니다.

using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }

    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
}

권한 부여 정책에 여러 권한 부여 요구 사항이 포함된 경우 정책 평가가 성공하려면 모든 요구 사항이 통과되어야 합니다. 즉, 단일 권한 부여 정책에 추가된 여러 권한 부여 요구 사항은 AND 기준으로 처리됩니다.

참고 항목

요구 사항에는 데이터 또는 속성이 필요하지 않습니다.

권한 부여 처리기

권한 부여 처리기는 요구 사항의 속성에 대한 평가를 담당합니다. 권한 부여 처리기는 제공된 AuthorizationHandlerContext 요구 사항을 평가하여 액세스가 허용되는지 확인합니다.

요구 사항에는 여러 처리기가 있을 수 있습니다. 처리기에서는 AuthorizationHandler<TRequirement>를 상속할 수 있으며, TRequirement은 처리 대상이 되는 요구 사항입니다. 또는 처리기가 둘 이상의 요구 사항을 처리하도록 구현 IAuthorizationHandler 할 수 있습니다.

하나의 요구 사항에 처리기 사용

다음 예제에서는 최소 연령 처리기가 단일 요구 사항을 처리하는 일 대 일 관계를 보여 줍니다.

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
                                        c.Issuer == "http://contoso.com"))
        {
            //TODO: Use the following if targeting a version of
            //.NET Framework older than 4.6:
            //      return Task.FromResult(0);
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth && 
                                        c.Issuer == "http://contoso.com").Value);

        int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

위의 코드는 현재 사용자 주체에 알려진 신뢰할 수 있는 발급자가 발급한 생년월일 클레임이 있는지 확인합니다. 클레임이 누락된 경우 권한 부여가 발생할 수 없으며, 이 경우 완료된 작업이 반환됩니다. 클레임이 있으면 사용자의 연령이 계산됩니다. 사용자가 요구 사항에 정의된 최소 연령을 충족하는 경우 권한 부여가 성공한 것으로 간주됩니다. 권한 부여가 성공하면 context.Succeed는 충족된 요구 사항을 유일한 매개 변수로 호출합니다.

여러 요구 사항에 처리기 사용

다음 예제에서는 권한 처리기가 세 가지 유형의 요구 사항을 처리할 수 있는 일 대 다 관계를 보여 줍니다.

using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource) ||
                    IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission ||
                     requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }

    private bool IsSponsor(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }
}

이전 코드에서는 성공적으로 표시되지 아니한 요구 사항을 포함하는 속성인 PendingRequirements을 트래버스합니다. ReadPermission 요구 사항의 경우 요청된 리소스에 액세스하려면 사용자가 소유자 또는 스폰서여야 합니다. EditPermission 또는 DeletePermission 요구 사항의 경우 요청된 리소스에 액세스하려면 소유자여야 합니다.

처리기 등록

구성 중에 서비스 컬렉션에 처리기를 등록합니다. 예시:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();
    services.AddRazorPages();
    services.AddAuthorization(options =>
    {
        options.AddPolicy("AtLeast21", policy =>
            policy.Requirements.Add(new MinimumAgeRequirement(21)));
    });

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
}

위의 코드에서는 services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();를 호출하여 MinimumAgeHandler를 싱글톤으로 등록합니다. 기본 제공 서비스 수명을 사용하여 처리기를 등록할 수 있습니다.

요구 사항과 처리기를 IAuthorizationRequirementIAuthorizationHandler 모두를 구현하는 단일 클래스로 번들로 묶을 수 있습니다. 이렇게 하면 처리기와 요구 사항이 긴밀하게 결합됩니다. 이는 간단한 요구 사항 및 처리기에만 권장됩니다. 두 인터페이스를 모두 구현하는 클래스를 만들면 요구 사항을 직접 처리할 수 있는 기본 제공 PassThroughAuthorizationHandler로 인해 DI에 처리기를 등록할 필요가 없습니다.

AssertionRequirement가 완전히 자체 포함된 클래스의 요구 사항 및 처리기인 좋은 예제는 AssertionRequirement 클래스를 참조하세요.

처리기는 무엇을 반환해야 합니까?

처리기 예제Handle 메서드는 값을 반환하지 않습니다. 성공 또는 실패 상태는 어떻게 표시하나요?

  • 처리기는 context.Succeed(IAuthorizationRequirement requirement)를 호출하여 성공적으로 유효성이 검사된 요구 사항을 전달함으로써 성공을 나타냅니다.

  • 동일한 요구 사항에 대한 다른 처리기가 성공할 수 있기 때문에 처리기는 일반적으로 오류를 처리할 필요가 없습니다.

  • 다른 요구 사항 처리기가 성공하더라도 실패를 보장하려면 context.Fail을 호출합니다.

처리기에서 context.Succeed 또는 context.Fail을 호출하는 경우 다른 모든 처리기가 계속 호출됩니다. 이렇게 하면 다른 처리기가 요구 사항의 유효성을 성공적으로 검사하거나 실패한 경우에도 로깅과 같은 부작용이 발생할 수 있습니다. 로 false설정하면 속성이 InvokeHandlersAfterFailure 호출되면 처리기 실행을 단락합니다 context.Fail . InvokeHandlersAfterFailure는 기본적으로 true로 설정되며, 이 경우 모든 처리기가 호출됩니다.

참고 항목

인증이 실패하더라도 권한 부여 처리기가 호출됩니다.

요구 사항에 대해 여러 처리기를 원하는 이유는 무엇인가요?

OR 기준으로 평가하려는 경우 단일 요구 사항에 대해 여러 처리기를 구현합니다. 예를 들어 Microsoft에는 키 카드로만 열리는 문이 있습니다. 키 카드를 집에 그대로 두면 수신자가 임시 스티커를 인쇄하고 문을 엽니다. 이 시나리오에서는 BuildingEntry라는 단일 요구 사항이 있지만 각 요구 사항을 검사하는 여러 처리기가 있습니다.

BuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "BadgeId" &&
                                       c.Issuer == "http://microsoftsecurity"))
        {
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs

using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using PoliciesAuthApp1.Services.Requirements;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
                                                   BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
                                       c.Issuer == "https://microsoftsecurity"))
        {
            // We'd also check the expiration date on the sticker.
            context.Succeed(requirement);
        }

        //TODO: Use the following if targeting a version of
        //.NET Framework older than 4.6:
        //      return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

두 처리기가 모두 등록되었는지 확인합니다. 정책이 BuildingEntryRequirement를 평가할 때 두 처리기가 성공하면 정책 평가가 성공합니다.

함수를 사용하여 정책 수행

정책을 이행하는 것이 코드로 표현하기 쉬운 상황이 있을 수 있습니다. RequireAssertion 정책 작성기를 사용하여 정책을 구성할 때 Func<AuthorizationHandlerContext, bool>을 제공할 수 있습니다.

예를 들어 이전 BadgeEntryHandler를 다음과 같이 다시 작성할 수 있습니다.

services.AddAuthorization(options =>
{
     options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                (c.Type == "BadgeId" ||
                 c.Type == "TemporaryBadgeId") &&
                 c.Issuer == "https://microsoftsecurity")));
});

처리기에서 MVC 요청 컨텍스트에 액세스

권한 부여 처리기에서 구현하는 HandleRequirementAsync 메서드에는 두 개의 매개 변수, 즉 처리 중인 AuthorizationHandlerContextTRequirement가 있습니다. MVC 또는 SignalR과 같은 프레임워크는 추가 정보를 전달하기 위해 개체를 AuthorizationHandlerContextResource 속성에 자유롭게 추가할 수 있습니다.

엔드포인트 라우팅을 사용하는 경우 권한 부여는 일반적으로 권한 부여 미들웨어에 의해 처리됩니다. 이 경우 Resource 속성은 HttpContext의 인스턴스입니다. 컨텍스트를 사용하여 현재 엔드포인트에 액세스할 수 있습니다. 이 엔드포인트는 라우팅하는 기본 리소스를 프로브하는 데 사용할 수 있습니다. 예시:

if (context.Resource is HttpContext httpContext)
{
    var endpoint = httpContext.GetEndpoint();
    var actionDescriptor = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();
    ...
}

기존 라우팅을 사용하거나 MVC 권한 부여 필터의 일부로 권한 부여가 발생하는 경우 Resource의 값은 AuthorizationFilterContext 인스턴스입니다. 이 속성은 MVC 및 Razor Pages에서 제공되는 HttpContext, RouteData 및 모든 항목에 대한 액세스를 제공합니다.

Resource 속성을 사용하는 것은 프레임워크와 관련이 있습니다. Resource 속성의 정보를 사용하면 권한 부여 정책이 특정 프레임워크로 제한됩니다. is 키워드를 사용하여 Resource 속성을 캐스팅한 다음, 다른 프레임워크에서 실행될 때 코드가 InvalidCastException과 충돌하지 않도록 캐스팅이 성공했는지 확인해야 합니다.

// Requires the following import:
//     using Microsoft.AspNetCore.Mvc.Filters;
if (context.Resource is AuthorizationFilterContext mvcContext)
{
    // Examine MVC-specific things like routing data.
}

전역적으로 모든 사용자를 인증하도록 요구

모든 사용자의 인증을 전역으로 요구하는 방법에 대한 자세한 내용은 인증된 사용자 요구를 참조하세요.