ASP.NET Core의 정책 기반 권한 부여Policy-based authorization in ASP.NET Core

역할 기반 권한 부여클레임 기반 권한 부여 는 내부적으로 요구 사항, 요구 사항 처리기, 그리고 미리 구성된 정책을 사용합니다.Underneath the covers, role-based authorization and claims-based authorization use a requirement, a requirement handler, and a pre-configured policy. 이런 빌딩 블록들은 권한 부여 평가를 코드로 표현할 수 있는 기능을 지원합니다.These building blocks support the expression of authorization evaluations in code. 결과적으로 보다 풍부하고 재사용 가능하며 테스트 가능한 권한 부여 구조를 만들 수 있습니다.The result is a richer, reusable, testable authorization structure.

권한 부여 정책은 하나 이상의 요구 사항으로 구성됩니다.An authorization policy consists of one or more requirements. 그리고 Startup.ConfigureServices에서 권한 부여 서비스 구성의 일부로 등록됩니다.It's registered as part of the authorization service configuration, in the Startup.ConfigureServices method:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

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

위의 예제는 "AtLeast21"이라는 정책을 생성합니다.In the preceding example, an "AtLeast21" policy is created. 이 정책은 요구 사항의 — 매개 변수로 제공되는, 최소 연령을 뜻하는 단일 요구 사항을 갖습니다.It has a single requirement—that of a minimum age, which is supplied as a parameter to the requirement.

IAuthorizationServiceIAuthorizationService

기본 인증은 성공 하는 경우를 결정 하는 서비스가 IAuthorizationService:The primary service that determines if authorization is successful is 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합니다.The preceding code highlights the two methods of the IAuthorizationService.

IAuthorizationRequirement 메서드가 없는 및 권한 부여는 성공 여부를 추적 하는 메커니즘을 사용 하 여 표식 서비스가입니다.IAuthorizationRequirement is a marker service with no methods, and the mechanism for tracking whether authorization is successful.

IAuthorizationHandler 요구 사항을 충족 하는 경우를 검사 합니다.Each IAuthorizationHandler is responsible for checking if requirements are met:

/// <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 클래스는 요구 사항이 충족 되었는지 여부를 표시 하는 처리기에서 사용 합니다.The AuthorizationHandlerContext class is what the handler uses to mark whether requirements have been met:

 context.Succeed(requirement)

다음 코드에서는 간단한 (고 주석을 사용 하 여 주석이 추가 된) 권한 부여 서비스의 기본 구현 합니다.The following code shows the simplified (and annotated with comments) default implementation of the authorization service:

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:The following code shows a typical 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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

사용 하 여 IAuthorizationService 또는 [Authorize(Policy = "Something"] 권한 부여에 대 한 합니다.Use IAuthorizationService or [Authorize(Policy = "Something"] for authorization.

MVC 컨트롤러에 정책을 적용Applying policies to MVC controllers

Razor 페이지를 사용 하는 경우 참조 Razor 페이지에 정책을 적용 이 문서의.If you're using Razor Pages, see Applying policies to Razor Pages in this document.

정책을 사용 하 여 컨트롤러에 적용 되는 [Authorize] 정책 이름의 특성입니다.Policies are applied to controllers by using the [Authorize] attribute with the policy name. 예를 들어:For example:

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

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

Razor 페이지에 정책 적용Applying policies to Razor Pages

정책을 사용 하 여 Razor 페이지에 적용 되는 [Authorize] 정책 이름의 특성입니다.Policies are applied to Razor Pages by using the [Authorize] attribute with the policy name. 예를 들어:For example:

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

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

정책 수를 사용 하 여도 Razor 페이지에 적용 하는 수는 권한 부여 규칙합니다.Policies can also be applied to Razor Pages by using an authorization convention.

요구 사항Requirements

권한 부여 요구 사항은 현재 사용자 보안 주체를 평가 하는 정책을 사용할 수 있는 데이터 매개 변수의 컬렉션입니다.An authorization requirement is a collection of data parameters that a policy can use to evaluate the current user principal. 요구 사항인 단일 매개 변수는 "AtLeast21" 정책에서—최소 보존 기간입니다.In our "AtLeast21" policy, the requirement is a single parameter—the minimum age. 요구 사항 구현 IAuthorizationRequirement, 빈 표식을 인터페이스인 합니다.A requirement implements IAuthorizationRequirement, which is an empty marker interface. 매개 변수가 있는 최소 보존 기간 요구 사항이 다음과 같이 구현할 수 있습니다.A parameterized minimum age requirement could be implemented as follows:

using Microsoft.AspNetCore.Authorization;

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

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

권한 부여 정책에 여러 권한 부여 요구 사항이 있으면 모든 요구 사항을 정책 평가가 성공 하기 위해 전달 해야 합니다.If an authorization policy contains multiple authorization requirements, all requirements must pass in order for the policy evaluation to succeed. 에 단일 권한 부여 정책에 추가 하는 여러 권한 부여 요구 사항을 처리 되는 즉, 한 AND 단위로 합니다.In other words, multiple authorization requirements added to a single authorization policy are treated on an AND basis.

참고

요구 사항이 데이터나 속성을 가져야 할 필요는 없습니다.A requirement doesn't need to have data or properties.

권한 부여 처리기Authorization handlers

권한 부여 처리기는 요구 사항의 속성을 평가하는 역할을 담당합니다.An authorization handler is responsible for the evaluation of a requirement's properties. 권한 부여 처리기는 제공된 AuthorizationHandlerContext에 대해 요구 사항을 평가하여 접근 허용 여부를 결정합니다.The authorization handler evaluates the requirements against a provided AuthorizationHandlerContext to determine if access is allowed.

하나의 요구 사항에 여러 개의 처리기 가 존재할 수 있습니다.A requirement can have multiple handlers. 처리기는 AuthorizationHandler<TRequirement>를 상속받으며, 여기서 TRequirement 는 처리해야 할 요구 사항입니다.A handler may inherit AuthorizationHandler<TRequirement>, where TRequirement is the requirement to be handled. 또는 처리기를 구현할 수 있습니다 IAuthorizationHandler 요구 사항 둘 이상의 유형을 처리 하도록 합니다.Alternatively, a handler may implement IAuthorizationHandler to handle more than one type of requirement.

한 가지 요구 사항에 대한 처리기 사용하기Use a handler for one requirement

다음은 최소 연령 처리기에서 단일 요구 사항을 사용하는 일대일 관계의 예제입니다.The following is an example of a one-to-one relationship in which a minimum age handler utilizes a single requirement:

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;
    }
}

이 코드는 먼저 현재 사용자의 신원이 우리가 알고 있고 신뢰할 수 있는 발급자로부터 발급된 생년월일 클레임을 갖고 있는지부터 확인합니다.The preceding code determines if the current user principal has a date of birth claim which has been issued by a known and trusted Issuer. 만약 클레임이 누락되었다면 권한을 부여할 수 없으므로 완료된 작업이 반환됩니다.Authorization can't occur when the claim is missing, in which case a completed task is returned. 반면 클레임이 존재할 경우, 사용자의 나이가 계산됩니다.When a claim is present, the user's age is calculated. 그리고 나이가 요구 사항에 의해 정의된 최소 연령을 만족할 경우 권한 부여가 성공한 것으로 간주됩니다.If the user meets the minimum age defined by the requirement, authorization is deemed successful. 권한 부여에 성공하면 만족한 요구 사항을 유일한 매개 변수로 전달하여 context.Succeed를 호출합니다.When authorization is successful, context.Succeed is invoked with the satisfied requirement as its sole parameter.

여러 요구 사항에 대한 처리기 사용하기Use a handler for multiple requirements

다음은 권한 처리기는 세 가지 유형의 요구 사항 처리할 수 있습니다-다 관계의 예입니다.The following is an example of a one-to-many relationship in which a permission handler can handle three different types of requirements:

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—성공으로 표시 되지 않습니다 요구 사항을 포함 하는 속성입니다.The preceding code traverses PendingRequirements—a property containing requirements not marked as successful. 에 대 한는 ReadPermission 요구 사항, 소유자 또는 스폰서 요청 된 리소스에 액세스 하는 사용자 여야 합니다.For a ReadPermission requirement, the user must be either an owner or a sponsor to access the requested resource. 경우에 EditPermission 또는 DeletePermission 요구 사항, 자신이 요청한 리소스에 액세스 하려면 소유자 여야 합니다.In the case of an EditPermission or DeletePermission requirement, he or she must be an owner to access the requested resource.

처리기 등록하기Handler registration

처리기는 구성 과정 중 서비스 컬렉션에 등록됩니다.Handlers are registered in the services collection during configuration. 예를 들어:For example:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

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

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

앞의 코드를 등록 MinimumAgeHandler 를 호출 하 여 단일 항목으로 services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();입니다.The preceding code registers MinimumAgeHandler as a singleton by invoking services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();. 기본 제공 중 하나를 사용 하 여 처리기를 등록할 수 있습니다 수명 서비스합니다.Handlers can be registered using any of the built-in service lifetimes.

처리기가 반환해야 하는 결과는?What should a handler return?

본문의 처리기 예제에서 Handle 메서드가 아무런 값도 반환하지 않는다는 점에 주목하시기 바랍니다.Note that the Handle method in the handler example returns no value. 그렇다면 성공 혹은 실패 여부는 어떻게 확인할 수 있을까요?How is a status of either success or failure indicated?

  • 처리기는 성공적으로 검증된 요구 사항을 context.Succeed(IAuthorizationRequirement requirement)에 매개 변수로 전달하여 호출함으로써 성공했음을 나타냅니다.A handler indicates success by calling context.Succeed(IAuthorizationRequirement requirement), passing the requirement that has been successfully validated.

  • 일반적으로 처리기는 실패를 처리할 필요가 없는데, 동일한 요구 사항에 대한 다른 처리기가 성공할 수도 있기 때문입니다.A handler doesn't need to handle failures generally, as other handlers for the same requirement may succeed.

  • 다른 처리기의 성공 여부와 관계없이 무조건 실패한 것으로 나타내려면 context.Fail을 호출합니다.To guarantee failure, even if other requirement handlers succeed, call context.Fail.

처리기를 호출 하는 경우 context.Succeed 또는 context.Fail, 다른 모든 처리기도 호출 됩니다.If a handler calls context.Succeed or context.Fail, all other handlers are still called. 이렇게 하면 다른 처리기가 성공적으로 유효성이 검사 되지 않았거나 요구 사항 실패 하는 경우에 수행 하는 로깅과 같은 부작용을 생성 하기 위한 요구 사항.This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. InvokeHandlersAfterFailure 속성이 (ASP.NET Core 1.1 이상에서 사용 가능) false로 설정되면 context.Fail이 호출될 경우 나머지 처리기들의 실행이 중단되고 즉시 빠져나갑니다.When set to false, the InvokeHandlersAfterFailure property (available in ASP.NET Core 1.1 and later) short-circuits the execution of handlers when context.Fail is called. InvokeHandlersAfterFailure의 기본값은 true로, 이 경우 모든 처리기가 호출됩니다.InvokeHandlersAfterFailure defaults to true, in which case all handlers are called.

참고

권한 부여 처리기는 인증에 실패 하는 경우에 호출 됩니다.Authorization handlers are called even if authentication fails.

요구 사항에 대해 여러 개의 처기리가 필요한 이유는?Why would I want multiple handlers for a requirement?

만약 OR 기반의 평가를 수행하고 싶다면 단일 요구 사항에 대해 여러 개의 처리기를 구현해야 합니다.In cases where you want evaluation to be on an OR basis, implement multiple handlers for a single requirement. 예를 들어, Microsoft에는 키 카드로만 열 수 있는 문이 있습니다.For example, Microsoft has doors which only open with key cards. 만약 키 카드를 집에 두고 왔다면 접수원이 임시 스티커를 인쇄하고 대신 문을 열어줍니다.If you leave your key card at home, the receptionist prints a temporary sticker and opens the door for you. 이 시나리오의 경우, 요구 사항은 BuildingEntry 하나지만 여러 처리기가 단일 요구 사항을 각각 개별적으로 검토하게 됩니다.In this scenario, you'd have a single requirement, BuildingEntry, but multiple handlers, each one examining a single requirement.

BuildingEntryRequirement.csBuildingEntryRequirement.cs

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.csBadgeEntryHandler.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.csTemporaryStickerHandler.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;
    }
}

두 처리기가 모두 등록되어 있는지 확인합니다.Ensure that both handlers are registered. 정책이 BuildingEntryRequirement 를 평가할 때, 두 처리기 중 하나라도 성공하면 정책 평가가 성공한 것으로 간주됩니다.If either handler succeeds when a policy evaluates the BuildingEntryRequirement, the policy evaluation succeeds.

func를 이용해서 정책 구성하기Using a func to fulfill a policy

코드로 정책을 표현해서 구성하는 편이 더 간단한 경우도 있습니다.There may be situations in which fulfilling a policy is simple to express in code. 정책을 구성할 때 RequireAssertion 정책 빌더에 Func<AuthorizationHandlerContext, bool>을 전달할 수 있습니다.It's possible to supply a Func<AuthorizationHandlerContext, bool> when configuring your policy with the RequireAssertion policy builder.

예를 들어 이전 BadgeEntryHandler를 다음과 같이 다시 작성할 수도 있습니다.For example, the previous BadgeEntryHandler could be rewritten as follows:

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 요청 컨텍스트 접근하기Accessing MVC request context in handlers

권한 부여 처리기에서 구현해야 하는 HandleRequirementAsync 메서드에는 AuthorizationHandlerContext와 처리해야 할 대상인 TRequirement라는 두 개의 매개 변수가 존재합니다.The HandleRequirementAsync method you implement in an authorization handler has two parameters: an AuthorizationHandlerContext and the TRequirement you are handling. MVC나 Jabbr 같은 프레임워크는 AuthorizationHandlerContextResource 속성에 자유롭게 개체를 추가해서 추가적인 정보를 전달할 수 있습니다.Frameworks such as MVC or Jabbr are free to add any object to the Resource property on the AuthorizationHandlerContext to pass extra information.

예를 들어, MVC는 Resource 속성에 AuthorizationFilterContext의 인스턴스를 전달합니다.For example, MVC passes an instance of AuthorizationFilterContext in the Resource property. 이 속성은 HttpContextRouteData를 비롯한, MVC 및 Razor 페이지가 제공하는 다양한 정보들에 대한 접근을 제공합니다.This property provides access to HttpContext, RouteData, and everything else provided by MVC and Razor Pages.

Resource 속성을 사용하는 방식은 프레임워크에 따라서 달라집니다.The use of the Resource property is framework specific. 따라서 Resource 속성의 정보를 사용할 경우 권한 부여 정책이 특정 프레임워크를 대상으로 제한될 수 있습니다.Using information in the Resource property limits your authorization policies to particular frameworks. 캐스팅 해야 합니다 Resource 사용 하 여 속성을 is 키워드를 하 고 확인 코드 충돌 하지 않도록 하려면 캐스팅 했습니다 사용 하 여는 InvalidCastException 다른 프레임 워크에서 실행할 때:You should cast the Resource property using the is keyword, and then confirm the cast has succeeded to ensure your code doesn't crash with an InvalidCastException when run on other frameworks:

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