ASP.NET Core中基于策略的授权

实际上,基于角色的授权基于声明的授权都使用要求、要求处理程序和预配置的策略。 这些构建基块支持在代码中使用授权评估表达式。 结果就是,授权结构更加丰富,可重复使用,并且可以测试。

授权策略包含一个或多个要求。 授权策略包含一个或多个要求,并在 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 Pages。

需求

授权要求是一个可供策略用来评估当前用户主体的数据参数的集合。 在我们的“AtLeast21”策略中,此要求是单个参数—最低年龄。 要求可以实现 IAuthorizationRequirement,这是一个空标记接口。 参数化的最低年龄要求可以按如下方式实现:

using Microsoft.AspNetCore.Authorization;

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

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

如果授权策略包含多个授权要求,则所有要求必须通过,才能成功进行策略评估。 换句话说,添加到单个授权策略中的多个授权要求将分别处理

备注

要求不需要具有数据或属性。

授权处理程序

授权处理程序负责评估要求的属性。 授权处理程序会针对提供的AuthorizationHandlerContext 来评估要求,确定是否允许访问。

一项要求可以有多个处理程序 处理程序可以继承 AuthorizationHandler<,其中的 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 要求,用户必须是所有者或主办方才能访问请求的资源。 如果是 EditPermissionDeletePermission 要求,则该用户必须是访问所请求资源的所有者。

处理程序注册

处理程序是在配置期间在服务集合中注册的。 例如:

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 注册为单一实例。 可以使用任何内置服务生存期来注册处理程序。

处理程序应返回什么?

请注意,Handle处理程序示例中的 方法不返回值。 如何表明状态是成功还是失败?

  • 处理程序通过调用 context.Succeed(IAuthorizationRequirement requirement) 并传递已成功验证的要求来表示成功。

  • 处理程序通常不需要处理失败,因为同一要求的其他处理程序可能会成功。

  • 为了保证故障,即使其他要求处理程序成功,也 context.Fail

如果处理程序调用 context.Succeedcontext.Fail,则仍将调用所有其他处理程序。 这允许要求产生副作用,如日志记录,即使另一个处理程序已成功验证或失败,也会发生这种情况。 当设置为falseInvokeHandlersAfterFailure属性 (位于 ASP.NET Core 1.1 和更高版本) 会使短路执行的处理程序时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 时,如果有一个处理程序成功,则策略评估成功。

使用 func 来实现策略

有些情况下,策略很容易用代码实现。 可以在通过 Func<AuthorizationHandlerContext, bool> 策略生成器配置策略时提供 RequireAssertion

例如,上一个 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 方法有两个参数:AuthorizationHandlerContext 以及你正在处理的 TRequirement MVC 或 Jabbr 之类的框架可以自由地将任何对象添加到 Resource 中的 AuthorizationHandlerContext 属性,以便传递额外信息。

例如,MVC 在 属性中传递 AuthorizationFilterContextResource 实例。 可以通过此属性访问 HttpContextRouteData 以及 MVC 和 Razor 页面提供的所有其他内容。

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

实际上,基于角色的授权基于声明的授权都使用要求、要求处理程序和预配置的策略。 这些构建基块支持在代码中使用授权评估表达式。 结果就是,授权结构更加丰富,可重复使用,并且可以测试。

授权策略包含一个或多个要求。 授权策略包含一个或多个要求,并在 Startup.ConfigureServices 方法中作为授权服务配置的一部分注册:

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”策略。 它只有一个要求—,即最低年龄,以参数的形式传递给要求。

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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

使用 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 Pages。

需求

授权要求是一个可供策略用来评估当前用户主体的数据参数的集合。 在我们的“AtLeast21”策略中,此要求是单个参数—最低年龄。 要求可以实现 IAuthorizationRequirement,这是一个空标记接口。 参数化的最低年龄要求可以按如下方式实现:

using Microsoft.AspNetCore.Authorization;

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

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

如果授权策略包含多个授权要求,则所有要求必须通过,才能成功进行策略评估。 换句话说,添加到单个授权策略中的多个授权要求将分别处理

备注

要求不需要具有数据或属性。

授权处理程序

授权处理程序负责评估要求的属性。 授权处理程序会针对提供的AuthorizationHandlerContext 来评估要求,确定是否允许访问。

一项要求可以有多个处理程序 处理程序可以继承 AuthorizationHandler<,其中的 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 要求,用户必须是所有者或主办方才能访问请求的资源。 如果是 EditPermissionDeletePermission 要求,则该用户必须是访问所请求资源的所有者。

处理程序注册

处理程序是在配置期间在服务集合中注册的。 例如:

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

前面的代码通过调用 services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();MinimumAgeHandler 注册为单一实例。 可以使用任何内置服务生存期来注册处理程序。

处理程序应返回什么?

请注意,Handle处理程序示例中的 方法不返回值。 如何表明状态是成功还是失败?

  • 处理程序通过调用 context.Succeed(IAuthorizationRequirement requirement) 并传递已成功验证的要求来表示成功。

  • 处理程序通常不需要处理失败,因为同一要求的其他处理程序可能会成功。

  • 为了保证故障,即使其他要求处理程序成功,也 context.Fail

如果处理程序调用 context.Succeedcontext.Fail,则仍将调用所有其他处理程序。 这允许要求产生副作用,如日志记录,即使另一个处理程序已成功验证或失败,也会发生这种情况。 当设置为falseInvokeHandlersAfterFailure属性 (位于 ASP.NET Core 1.1 和更高版本) 会使短路执行的处理程序时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 时,如果有一个处理程序成功,则策略评估成功。

使用 func 来实现策略

有些情况下,策略很容易用代码实现。 可以在通过 Func<AuthorizationHandlerContext, bool> 策略生成器配置策略时提供 RequireAssertion

例如,上一个 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 方法有两个参数:AuthorizationHandlerContext 以及你正在处理的 TRequirement MVC 或 Jabbr 之类的框架可以自由地将任何对象添加到 Resource 中的 AuthorizationHandlerContext 属性,以便传递额外信息。

例如,MVC 在 属性中传递 AuthorizationFilterContextResource 实例。 可以通过此属性访问 HttpContextRouteData 以及 MVC 和 Razor 页面提供的所有其他内容。

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

上一篇:ASP.NET Core 中的基于声明的授权

下一篇:在 ASP.NET Core 中使用 IAuthorizationPolicyProvider 的自定义授权策略提供程序

关注微信小程序
程序员编程王-随时随地学编程

扫描二维码
程序员编程王

扫一扫关注最新编程教程