ASP.NET Core中基于策略的授权Policy-based authorization in ASP.NET Core

本文内容

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

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

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews();
  4. services.AddRazorPages();
  5. services.AddAuthorization(options =>
  6. {
  7. options.AddPolicy("AtLeast21", policy =>
  8. policy.Requirements.Add(new MinimumAgeRequirement(21)));
  9. });
  10. }

在前面的示例中,创建了一个“AtLeast21”策略。它只有最短期限—的一种要求,它作为要求的参数提供。

IAuthorizationServiceIAuthorizationService

确定授权是否成功的主要服务是 IAuthorizationService

  1. /// <summary>
  2. /// Checks policy based permissions for a user
  3. /// </summary>
  4. public interface IAuthorizationService
  5. {
  6. /// <summary>
  7. /// Checks if a user meets a specific set of requirements for the specified resource
  8. /// </summary>
  9. /// <param name="user">The user to evaluate the requirements against.</param>
  10. /// <param name="resource">
  11. /// An optional resource the policy should be checked with.
  12. /// If a resource is not required for policy evaluation you may pass null as the value
  13. /// </param>
  14. /// <param name="requirements">The requirements to evaluate.</param>
  15. /// <returns>
  16. /// A flag indicating whether authorization has succeeded.
  17. /// This value is <value>true</value> when the user fulfills the policy;
  18. /// otherwise <value>false</value>.
  19. /// </returns>
  20. /// <remarks>
  21. /// Resource is an optional parameter and may be null. Please ensure that you check
  22. /// it is not null before acting upon it.
  23. /// </remarks>
  24. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource,
  25. IEnumerable<IAuthorizationRequirement> requirements);
  26. /// <summary>
  27. /// Checks if a user meets a specific authorization policy
  28. /// </summary>
  29. /// <param name="user">The user to check the policy against.</param>
  30. /// <param name="resource">
  31. /// An optional resource the policy should be checked with.
  32. /// If a resource is not required for policy evaluation you may pass null as the value
  33. /// </param>
  34. /// <param name="policyName">The name of the policy to check against a specific
  35. /// context.</param>
  36. /// <returns>
  37. /// A flag indicating whether authorization has succeeded.
  38. /// Returns a flag indicating whether the user, and optional resource has fulfilled
  39. /// the policy.
  40. /// <value>true</value> when the policy has been fulfilled;
  41. /// otherwise <value>false</value>.
  42. /// </returns>
  43. /// <remarks>
  44. /// Resource is an optional parameter and may be null. Please ensure that you check
  45. /// it is not null before acting upon it.
  46. /// </remarks>
  47. Task<AuthorizationResult> AuthorizeAsync(
  48. ClaimsPrincipal user, object resource, string policyName);
  49. }

前面的代码突出显示了IAuthorizationService的两种方法。

IAuthorizationRequirement 是一种不带方法的标记服务,以及用于跟踪授权是否成功的机制。

每个 IAuthorizationHandler 负责检查是否满足要求:

  1. /// <summary>
  2. /// Classes implementing this interface are able to make a decision if authorization
  3. /// is allowed.
  4. /// </summary>
  5. public interface IAuthorizationHandler
  6. {
  7. /// <summary>
  8. /// Makes a decision if authorization is allowed.
  9. /// </summary>
  10. /// <param name="context">The authorization information.</param>
  11. Task HandleAsync(AuthorizationHandlerContext context);
  12. }

处理程序使用 AuthorizationHandlerContext 类来标记是否已满足要求:

  1. context.Succeed(requirement)

下面的代码显示授权服务的默认实现(和批注批注)的默认实现:

  1. public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
  2. object resource, IEnumerable<IAuthorizationRequirement> requirements)
  3. {
  4. // Create a tracking context from the authorization inputs.
  5. var authContext = _contextFactory.CreateContext(requirements, user, resource);
  6. // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
  7. var handlers = await _handlers.GetHandlersAsync(authContext);
  8. // Invoke all handlers.
  9. foreach (var handler in handlers)
  10. {
  11. await handler.HandleAsync(authContext);
  12. }
  13. // Check the context, by default success is when all requirements have been met.
  14. return _evaluator.Evaluate(authContext);
  15. }

下面的代码演示典型 ConfigureServices

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // Add all of your handlers to DI.
  4. services.AddSingleton<IAuthorizationHandler, MyHandler1>();
  5. // MyHandler2, ...
  6. services.AddSingleton<IAuthorizationHandler, MyHandlerN>();
  7. // Configure your policies
  8. services.AddAuthorization(options =>
  9. options.AddPolicy("Something",
  10. policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));
  11. services.AddControllersWithViews();
  12. services.AddRazorPages();
  13. }

使用 IAuthorizationService[Authorize(Policy = "Something")] 进行授权。

将策略应用到 MVC 控制器Applying policies to MVC controllers

如果使用 Razor Pages,请参阅本文档中的将策略应用于 Razor Pages

策略通过使用具有策略名称的 [Authorize] 属性应用到控制器。例如:

  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.Mvc;
  3. [Authorize(Policy = "AtLeast21")]
  4. public class AlcoholPurchaseController : Controller
  5. {
  6. public IActionResult Index() => View();
  7. }

将策略应用到 Razor PagesApplying policies to Razor Pages

通过将 [Authorize] 属性与策略名称一起使用,将策略应用到 Razor Pages。例如:

  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.Mvc.RazorPages;
  3. [Authorize(Policy = "AtLeast21")]
  4. public class AlcoholPurchaseModel : PageModel
  5. {
  6. }

还可以通过使用授权约定,将策略应用到 Razor Pages。

要求Requirements

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

  1. using Microsoft.AspNetCore.Authorization;
  2. public class MinimumAgeRequirement : IAuthorizationRequirement
  3. {
  4. public int MinimumAge { get; }
  5. public MinimumAgeRequirement(int minimumAge)
  6. {
  7. MinimumAge = minimumAge;
  8. }
  9. }

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

备注

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

授权处理程序Authorization handlers

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

要求可以有多个处理程序处理程序可能会继承AuthorizationHandler<TRequirement >,其中 TRequirement 是需要处理的。或者,处理程序可以实现IAuthorizationHandler来处理多种类型的要求。

为一个要求使用处理程序Use a handler for one requirement

下面是一对一关系的示例,其中的单个最低年龄要求处理程序使用单个要求:

  1. using System;
  2. using System.Security.Claims;
  3. using System.Threading.Tasks;
  4. using Microsoft.AspNetCore.Authorization;
  5. using PoliciesAuthApp1.Services.Requirements;
  6. public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
  7. {
  8. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  9. MinimumAgeRequirement requirement)
  10. {
  11. if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
  12. c.Issuer == "http://contoso.com"))
  13. {
  14. //TODO: Use the following if targeting a version of
  15. //.NET Framework older than 4.6:
  16. // return Task.FromResult(0);
  17. return Task.CompletedTask;
  18. }
  19. var dateOfBirth = Convert.ToDateTime(
  20. context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth &&
  21. c.Issuer == "http://contoso.com").Value);
  22. int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
  23. if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
  24. {
  25. calculatedAge--;
  26. }
  27. if (calculatedAge >= requirement.MinimumAge)
  28. {
  29. context.Succeed(requirement);
  30. }
  31. //TODO: Use the following if targeting a version of
  32. //.NET Framework older than 4.6:
  33. // return Task.FromResult(0);
  34. return Task.CompletedTask;
  35. }
  36. }

前面的代码确定当前的用户主体是否有一个由已知的受信任颁发者颁发的出生日期声明。缺少声明时,无法进行授权,这种情况下会返回已完成的任务。存在声明时,会计算用户的年龄。如果用户满足此要求所定义的最低年龄,则可以认为授权成功。授权成功后,会调用 context.Succeed,并将满足要求作为其唯一参数。

为多个要求使用处理程序Use a handler for multiple requirements

下面是一个一对多关系的示例,其中权限处理程序可以处理三种不同类型的要求:

  1. using System.Linq;
  2. using System.Security.Claims;
  3. using System.Threading.Tasks;
  4. using Microsoft.AspNetCore.Authorization;
  5. using PoliciesAuthApp1.Services.Requirements;
  6. public class PermissionHandler : IAuthorizationHandler
  7. {
  8. public Task HandleAsync(AuthorizationHandlerContext context)
  9. {
  10. var pendingRequirements = context.PendingRequirements.ToList();
  11. foreach (var requirement in pendingRequirements)
  12. {
  13. if (requirement is ReadPermission)
  14. {
  15. if (IsOwner(context.User, context.Resource) ||
  16. IsSponsor(context.User, context.Resource))
  17. {
  18. context.Succeed(requirement);
  19. }
  20. }
  21. else if (requirement is EditPermission ||
  22. requirement is DeletePermission)
  23. {
  24. if (IsOwner(context.User, context.Resource))
  25. {
  26. context.Succeed(requirement);
  27. }
  28. }
  29. }
  30. //TODO: Use the following if targeting a version of
  31. //.NET Framework older than 4.6:
  32. // return Task.FromResult(0);
  33. return Task.CompletedTask;
  34. }
  35. private bool IsOwner(ClaimsPrincipal user, object resource)
  36. {
  37. // Code omitted for brevity
  38. return true;
  39. }
  40. private bool IsSponsor(ClaimsPrincipal user, object resource)
  41. {
  42. // Code omitted for brevity
  43. return true;
  44. }
  45. }

前面的代码遍历PendingRequirements—包含未标记为成功的要求的属性。对于 ReadPermission 要求,用户必须是所有者或主办方才能访问请求的资源。如果是 EditPermissionDeletePermission 要求,则该用户必须是访问所请求资源的所有者。

处理程序注册Handler registration

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

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews();
  4. services.AddRazorPages();
  5. services.AddAuthorization(options =>
  6. {
  7. options.AddPolicy("AtLeast21", policy =>
  8. policy.Requirements.Add(new MinimumAgeRequirement(21)));
  9. });
  10. services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
  11. }

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

处理程序应返回什么?What should a handler return?

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

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

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

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

如果处理程序调用 context.Succeedcontext.Fail,则仍将调用所有其他处理程序。这允许要求产生副作用,如日志记录,即使另一个处理程序已成功验证或失败,也会发生这种情况。如果设置为 "false",则在调用 context.Fail 时, InvokeHandlersAfterFailure属性(在1.1 和更高版本 ASP.NET Core 中提供)会将处理程序的执行操作短路。InvokeHandlersAfterFailure 默认为 true,在这种情况下,将调用所有处理程序。

备注

即使身份验证失败,也会调用授权处理程序。

为什么需要对一项要求使用多个处理程序?Why would I want multiple handlers for a requirement?

如果希望计算基于,请为单个要求实现多个处理程序。例如,Microsoft 的门只能使用门禁卡打开。如果你将门禁卡丢在家中,可以要求前台打印一张临时标签来开门。在这种情况下,你将有一个要求BuildingEntry,但有多个处理程序,每个处理程序都检查单个需求。

BuildingEntryRequirement.cs

  1. using Microsoft.AspNetCore.Authorization;
  2. public class BuildingEntryRequirement : IAuthorizationRequirement
  3. {
  4. }

BadgeEntryHandler.cs

  1. using System.Security.Claims;
  2. using System.Threading.Tasks;
  3. using Microsoft.AspNetCore.Authorization;
  4. using PoliciesAuthApp1.Services.Requirements;
  5. public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
  6. {
  7. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  8. BuildingEntryRequirement requirement)
  9. {
  10. if (context.User.HasClaim(c => c.Type == "BadgeId" &&
  11. c.Issuer == "http://microsoftsecurity"))
  12. {
  13. context.Succeed(requirement);
  14. }
  15. //TODO: Use the following if targeting a version of
  16. //.NET Framework older than 4.6:
  17. // return Task.FromResult(0);
  18. return Task.CompletedTask;
  19. }
  20. }

TemporaryStickerHandler.cs

  1. using System.Security.Claims;
  2. using System.Threading.Tasks;
  3. using Microsoft.AspNetCore.Authorization;
  4. using PoliciesAuthApp1.Services.Requirements;
  5. public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
  6. {
  7. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  8. BuildingEntryRequirement requirement)
  9. {
  10. if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
  11. c.Issuer == "https://microsoftsecurity"))
  12. {
  13. // We'd also check the expiration date on the sticker.
  14. context.Succeed(requirement);
  15. }
  16. //TODO: Use the following if targeting a version of
  17. //.NET Framework older than 4.6:
  18. // return Task.FromResult(0);
  19. return Task.CompletedTask;
  20. }
  21. }

确保两个处理程序都已注册如果某个处理程序在某一策略评估 BuildingEntryRequirement时成功,则策略评估将成功。

使用 func 来实现策略Using a func to fulfill a policy

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

例如,可以按如下所示重写上一 BadgeEntryHandler

  1. services.AddAuthorization(options =>
  2. {
  3. options.AddPolicy("BadgeEntry", policy =>
  4. policy.RequireAssertion(context =>
  5. context.User.HasClaim(c =>
  6. (c.Type == "BadgeId" ||
  7. c.Type == "TemporaryBadgeId") &&
  8. c.Issuer == "https://microsoftsecurity")));
  9. });

访问处理程序中的 MVC 请求上下文Accessing MVC request context in handlers

在授权处理程序中实现的 HandleRequirementAsync 方法具有两个参数: AuthorizationHandlerContext 和正在处理的 TRequirementMVC 或 Jabbr 等框架可自由地将任何对象添加到 AuthorizationHandlerContext 上的 Resource 属性,以传递额外的信息。

例如,MVC 在 Resource 属性中传递AuthorizationFilterContext的实例。此属性提供对 HttpContextRouteData以及 MVC 和 Razor Pages 提供的所有其他内容的访问权限。

使用 Resource 属性是特定于框架的。使用 Resource 属性中的信息可将授权策略限制为特定框架。应使用 is 关键字强制转换 Resource 属性,然后确认强制转换已成功,以确保在其他框架上运行时代码不会崩溃 InvalidCastException

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

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

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

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
  4. services.AddAuthorization(options =>
  5. {
  6. options.AddPolicy("AtLeast21", policy =>
  7. policy.Requirements.Add(new MinimumAgeRequirement(21)));
  8. });
  9. }

在前面的示例中,创建了一个“AtLeast21”策略。它只有最短期限—的一种要求,它作为要求的参数提供。

IAuthorizationServiceIAuthorizationService

确定授权是否成功的主要服务是 IAuthorizationService

  1. /// <summary>
  2. /// Checks policy based permissions for a user
  3. /// </summary>
  4. public interface IAuthorizationService
  5. {
  6. /// <summary>
  7. /// Checks if a user meets a specific set of requirements for the specified resource
  8. /// </summary>
  9. /// <param name="user">The user to evaluate the requirements against.</param>
  10. /// <param name="resource">
  11. /// An optional resource the policy should be checked with.
  12. /// If a resource is not required for policy evaluation you may pass null as the value
  13. /// </param>
  14. /// <param name="requirements">The requirements to evaluate.</param>
  15. /// <returns>
  16. /// A flag indicating whether authorization has succeeded.
  17. /// This value is <value>true</value> when the user fulfills the policy;
  18. /// otherwise <value>false</value>.
  19. /// </returns>
  20. /// <remarks>
  21. /// Resource is an optional parameter and may be null. Please ensure that you check
  22. /// it is not null before acting upon it.
  23. /// </remarks>
  24. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource,
  25. IEnumerable<IAuthorizationRequirement> requirements);
  26. /// <summary>
  27. /// Checks if a user meets a specific authorization policy
  28. /// </summary>
  29. /// <param name="user">The user to check the policy against.</param>
  30. /// <param name="resource">
  31. /// An optional resource the policy should be checked with.
  32. /// If a resource is not required for policy evaluation you may pass null as the value
  33. /// </param>
  34. /// <param name="policyName">The name of the policy to check against a specific
  35. /// context.</param>
  36. /// <returns>
  37. /// A flag indicating whether authorization has succeeded.
  38. /// Returns a flag indicating whether the user, and optional resource has fulfilled
  39. /// the policy.
  40. /// <value>true</value> when the policy has been fulfilled;
  41. /// otherwise <value>false</value>.
  42. /// </returns>
  43. /// <remarks>
  44. /// Resource is an optional parameter and may be null. Please ensure that you check
  45. /// it is not null before acting upon it.
  46. /// </remarks>
  47. Task<AuthorizationResult> AuthorizeAsync(
  48. ClaimsPrincipal user, object resource, string policyName);
  49. }

前面的代码突出显示了IAuthorizationService的两种方法。

IAuthorizationRequirement 是一种不带方法的标记服务,以及用于跟踪授权是否成功的机制。

每个 IAuthorizationHandler 负责检查是否满足要求:

  1. /// <summary>
  2. /// Classes implementing this interface are able to make a decision if authorization
  3. /// is allowed.
  4. /// </summary>
  5. public interface IAuthorizationHandler
  6. {
  7. /// <summary>
  8. /// Makes a decision if authorization is allowed.
  9. /// </summary>
  10. /// <param name="context">The authorization information.</param>
  11. Task HandleAsync(AuthorizationHandlerContext context);
  12. }

处理程序使用 AuthorizationHandlerContext 类来标记是否已满足要求:

  1. context.Succeed(requirement)

下面的代码显示授权服务的默认实现(和批注批注)的默认实现:

  1. public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user,
  2. object resource, IEnumerable<IAuthorizationRequirement> requirements)
  3. {
  4. // Create a tracking context from the authorization inputs.
  5. var authContext = _contextFactory.CreateContext(requirements, user, resource);
  6. // By default this returns an IEnumerable<IAuthorizationHandlers> from DI.
  7. var handlers = await _handlers.GetHandlersAsync(authContext);
  8. // Invoke all handlers.
  9. foreach (var handler in handlers)
  10. {
  11. await handler.HandleAsync(authContext);
  12. }
  13. // Check the context, by default success is when all requirements have been met.
  14. return _evaluator.Evaluate(authContext);
  15. }

下面的代码演示典型 ConfigureServices

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. // Add all of your handlers to DI.
  4. services.AddSingleton<IAuthorizationHandler, MyHandler1>();
  5. // MyHandler2, ...
  6. services.AddSingleton<IAuthorizationHandler, MyHandlerN>();
  7. // Configure your policies
  8. services.AddAuthorization(options =>
  9. options.AddPolicy("Something",
  10. policy => policy.RequireClaim("Permission", "CanViewPage", "CanViewAnything")));
  11. services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
  12. }

使用 IAuthorizationService[Authorize(Policy = "Something")] 进行授权。

将策略应用到 MVC 控制器Applying policies to MVC controllers

如果使用 Razor Pages,请参阅本文档中的将策略应用于 Razor Pages

策略通过使用具有策略名称的 [Authorize] 属性应用到控制器。例如:

  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.Mvc;
  3. [Authorize(Policy = "AtLeast21")]
  4. public class AlcoholPurchaseController : Controller
  5. {
  6. public IActionResult Index() => View();
  7. }

将策略应用到 Razor PagesApplying policies to Razor Pages

通过将 [Authorize] 属性与策略名称一起使用,将策略应用到 Razor Pages。例如:

  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.Mvc.RazorPages;
  3. [Authorize(Policy = "AtLeast21")]
  4. public class AlcoholPurchaseModel : PageModel
  5. {
  6. }

还可以通过使用授权约定,将策略应用到 Razor Pages。

要求Requirements

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

  1. using Microsoft.AspNetCore.Authorization;
  2. public class MinimumAgeRequirement : IAuthorizationRequirement
  3. {
  4. public int MinimumAge { get; }
  5. public MinimumAgeRequirement(int minimumAge)
  6. {
  7. MinimumAge = minimumAge;
  8. }
  9. }

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

备注

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

授权处理程序Authorization handlers

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

要求可以有多个处理程序处理程序可能会继承AuthorizationHandler<TRequirement >,其中 TRequirement 是需要处理的。或者,处理程序可以实现IAuthorizationHandler来处理多种类型的要求。

为一个要求使用处理程序Use a handler for one requirement

下面是一对一关系的示例,其中的单个最低年龄要求处理程序使用单个要求:

  1. using System;
  2. using System.Security.Claims;
  3. using System.Threading.Tasks;
  4. using Microsoft.AspNetCore.Authorization;
  5. using PoliciesAuthApp1.Services.Requirements;
  6. public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
  7. {
  8. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  9. MinimumAgeRequirement requirement)
  10. {
  11. if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
  12. c.Issuer == "http://contoso.com"))
  13. {
  14. //TODO: Use the following if targeting a version of
  15. //.NET Framework older than 4.6:
  16. // return Task.FromResult(0);
  17. return Task.CompletedTask;
  18. }
  19. var dateOfBirth = Convert.ToDateTime(
  20. context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth &&
  21. c.Issuer == "http://contoso.com").Value);
  22. int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
  23. if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
  24. {
  25. calculatedAge--;
  26. }
  27. if (calculatedAge >= requirement.MinimumAge)
  28. {
  29. context.Succeed(requirement);
  30. }
  31. //TODO: Use the following if targeting a version of
  32. //.NET Framework older than 4.6:
  33. // return Task.FromResult(0);
  34. return Task.CompletedTask;
  35. }
  36. }

前面的代码确定当前的用户主体是否有一个由已知的受信任颁发者颁发的出生日期声明。缺少声明时,无法进行授权,这种情况下会返回已完成的任务。存在声明时,会计算用户的年龄。如果用户满足此要求所定义的最低年龄,则可以认为授权成功。授权成功后,会调用 context.Succeed,并将满足要求作为其唯一参数。

为多个要求使用处理程序Use a handler for multiple requirements

下面是一个一对多关系的示例,其中权限处理程序可以处理三种不同类型的要求:

  1. using System.Linq;
  2. using System.Security.Claims;
  3. using System.Threading.Tasks;
  4. using Microsoft.AspNetCore.Authorization;
  5. using PoliciesAuthApp1.Services.Requirements;
  6. public class PermissionHandler : IAuthorizationHandler
  7. {
  8. public Task HandleAsync(AuthorizationHandlerContext context)
  9. {
  10. var pendingRequirements = context.PendingRequirements.ToList();
  11. foreach (var requirement in pendingRequirements)
  12. {
  13. if (requirement is ReadPermission)
  14. {
  15. if (IsOwner(context.User, context.Resource) ||
  16. IsSponsor(context.User, context.Resource))
  17. {
  18. context.Succeed(requirement);
  19. }
  20. }
  21. else if (requirement is EditPermission ||
  22. requirement is DeletePermission)
  23. {
  24. if (IsOwner(context.User, context.Resource))
  25. {
  26. context.Succeed(requirement);
  27. }
  28. }
  29. }
  30. //TODO: Use the following if targeting a version of
  31. //.NET Framework older than 4.6:
  32. // return Task.FromResult(0);
  33. return Task.CompletedTask;
  34. }
  35. private bool IsOwner(ClaimsPrincipal user, object resource)
  36. {
  37. // Code omitted for brevity
  38. return true;
  39. }
  40. private bool IsSponsor(ClaimsPrincipal user, object resource)
  41. {
  42. // Code omitted for brevity
  43. return true;
  44. }
  45. }

前面的代码遍历PendingRequirements—包含未标记为成功的要求的属性。对于 ReadPermission 要求,用户必须是所有者或主办方才能访问请求的资源。如果是 EditPermissionDeletePermission 要求,则该用户必须是访问所请求资源的所有者。

处理程序注册Handler registration

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

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
  4. services.AddAuthorization(options =>
  5. {
  6. options.AddPolicy("AtLeast21", policy =>
  7. policy.Requirements.Add(new MinimumAgeRequirement(21)));
  8. });
  9. services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
  10. }

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

处理程序应返回什么?What should a handler return?

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

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

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

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

如果处理程序调用 context.Succeedcontext.Fail,则仍将调用所有其他处理程序。这允许要求产生副作用,如日志记录,即使另一个处理程序已成功验证或失败,也会发生这种情况。如果设置为 "false",则在调用 context.Fail 时, InvokeHandlersAfterFailure属性(在1.1 和更高版本 ASP.NET Core 中提供)会将处理程序的执行操作短路。InvokeHandlersAfterFailure 默认为 true,在这种情况下,将调用所有处理程序。

备注

即使身份验证失败,也会调用授权处理程序。

为什么需要对一项要求使用多个处理程序?Why would I want multiple handlers for a requirement?

如果希望计算基于,请为单个要求实现多个处理程序。例如,Microsoft 的门只能使用门禁卡打开。如果你将门禁卡丢在家中,可以要求前台打印一张临时标签来开门。在这种情况下,你将有一个要求BuildingEntry,但有多个处理程序,每个处理程序都检查单个需求。

BuildingEntryRequirement.cs

  1. using Microsoft.AspNetCore.Authorization;
  2. public class BuildingEntryRequirement : IAuthorizationRequirement
  3. {
  4. }

BadgeEntryHandler.cs

  1. using System.Security.Claims;
  2. using System.Threading.Tasks;
  3. using Microsoft.AspNetCore.Authorization;
  4. using PoliciesAuthApp1.Services.Requirements;
  5. public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
  6. {
  7. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  8. BuildingEntryRequirement requirement)
  9. {
  10. if (context.User.HasClaim(c => c.Type == "BadgeId" &&
  11. c.Issuer == "http://microsoftsecurity"))
  12. {
  13. context.Succeed(requirement);
  14. }
  15. //TODO: Use the following if targeting a version of
  16. //.NET Framework older than 4.6:
  17. // return Task.FromResult(0);
  18. return Task.CompletedTask;
  19. }
  20. }

TemporaryStickerHandler.cs

  1. using System.Security.Claims;
  2. using System.Threading.Tasks;
  3. using Microsoft.AspNetCore.Authorization;
  4. using PoliciesAuthApp1.Services.Requirements;
  5. public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
  6. {
  7. protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
  8. BuildingEntryRequirement requirement)
  9. {
  10. if (context.User.HasClaim(c => c.Type == "TemporaryBadgeId" &&
  11. c.Issuer == "https://microsoftsecurity"))
  12. {
  13. // We'd also check the expiration date on the sticker.
  14. context.Succeed(requirement);
  15. }
  16. //TODO: Use the following if targeting a version of
  17. //.NET Framework older than 4.6:
  18. // return Task.FromResult(0);
  19. return Task.CompletedTask;
  20. }
  21. }

确保两个处理程序都已注册如果某个处理程序在某一策略评估 BuildingEntryRequirement时成功,则策略评估将成功。

使用 func 来实现策略Using a func to fulfill a policy

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

例如,可以按如下所示重写上一 BadgeEntryHandler

  1. services.AddAuthorization(options =>
  2. {
  3. options.AddPolicy("BadgeEntry", policy =>
  4. policy.RequireAssertion(context =>
  5. context.User.HasClaim(c =>
  6. (c.Type == "BadgeId" ||
  7. c.Type == "TemporaryBadgeId") &&
  8. c.Issuer == "https://microsoftsecurity")));
  9. });

访问处理程序中的 MVC 请求上下文Accessing MVC request context in handlers

在授权处理程序中实现的 HandleRequirementAsync 方法具有两个参数: AuthorizationHandlerContext 和正在处理的 TRequirementMVC 或 Jabbr 等框架可自由地将任何对象添加到 AuthorizationHandlerContext 上的 Resource 属性,以传递额外的信息。

例如,MVC 在 Resource 属性中传递AuthorizationFilterContext的实例。此属性提供对 HttpContextRouteData以及 MVC 和 Razor Pages 提供的所有其他内容的访问权限。

使用 Resource 属性是特定于框架的。使用 Resource 属性中的信息可将授权策略限制为特定框架。应使用 is 关键字强制转换 Resource 属性,然后确认强制转换已成功,以确保在其他框架上运行时代码不会崩溃 InvalidCastException

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