光定义策略还不够,还必须向授权中间件注册策略。为此,请在 Startup 类的 ConfigureServices 方法中,将授权中间件添加为服务,如下所示:
services.AddAuthorization(options=>{ options.AddPolicy("ContentsEditor", policy => { policy.AddAuthenticationSchemes("Cookie, Bearer"); policy.RequireAuthenticatedUser(); policy.RequireRole("Admin"); policy.RequireClaim("editor", "contents"); });}
添加到中间件的每个策略都有一个名称,用于在 Controller 类的 Authorize 属性中引用策略:
[Authorize(Policy = "ContentsEditor")]publicIActionResult Save(Article article){ // ...}
使用 Authorize 属性,可以声明的方式设置策略,但也可以通过操作方法以编程方式调用策略,如图 2所示。
图 2:以编程方式检查策略
publicclassAdminController : Controller{ privateIAuthorizationService _authorization; publicAdminController(IAuthorizationService authorizationService) { _authorization = authorizationService; } publicasyncTask{ options.AddPolicy("ContentsEditor", policy => { policy.AddAuthenticationSchemes("Cookie, Bearer"); policy.RequireAuthenticatedUser(); policy.RequireRole("Admin"); policy.RequireClaim("editor", "contents"); });}
添加到中间件的每个策略都有一个名称,用于在 Controller 类的 Authorize 属性中引用策略:
[Authorize(Policy = "ContentsEditor")]publicIActionResult Save(Article article){ // ...}
使用 Authorize 属性,可以声明的方式设置策略,但也可以通过操作方法以编程方式调用策略,如图 2所示。
图 2:以编程方式检查策略
publicclassAdminController : Controller{ privateIAuthorizationService _authorization; publicAdminController(IAuthorizationService authorizationService) { _authorization = authorizationService; } publicasyncTask { policy.AddAuthenticationSchemes("Cookie, Bearer"); policy.RequireAuthenticatedUser(); policy.RequireRole("Admin"); policy.RequireClaim("editor", "contents"); });}
添加到中间件的每个策略都有一个名称ASPCMS批量添加产品 ,用于在 Controller 类的 Authorize 属性中引用策略:
[Authorize(Policy = "ContentsEditor")]publicIActionResult Save(Article article){ // ...}
使用 Authorize 属性,可以声明的方式设置策略,但也可以通过操作方法以编程方式调用策略,如图 2所示。
图 2:以编程方式检查策略
publicclassAdminController : Controller{ privateIAuthorizationService _authorization; publicAdminController(IAuthorizationService authorizationService) { _authorization = authorizationService; } publicasyncTask Save(Article article) { varallowed = await_authorization.AuthorizeAsync( User, "ContentsEditor")); if(!allowed) returnnewForbiddenResult(); // Proceed with the method implementation ... }}
如果无法以编程方式检查权限,建议返回 ForbiddenResult 对象。另一种选择是,返回 ChallengeResult 对象。
在 ASP.NET Core 1.x 中ASPCMS批量删除关键词 ,返回质询会指示授权中间件返回 401 状态代码,或将用户重定向到登录页,具体视配置而定。
不过,ASP.NET Core 2.0 中不会发生重定向;即使在 ASP.NET Core 1.x 中,如果用户已登录,质询最终也会指示返回 ForbiddenResult 对象。最后看来,最好的方法是在无法检查权限时返回 ForbiddenResult 对象。
请注意,甚至可以在 Razor 视图中以编程方式检查策略,如下面的代码所示:
@{ varauthorized = awaitAuthorization.AuthorizeAsync( User, "ContentsEditor"))}@if(!authorized){ You’re not authorized to access thispage.
}
不过,为了让此代码能够正常运行,必须先注入对授权服务的依赖ASPCMS批量上传内容 ,如下所示:
@inject IAuthorizationService Authorization
在视图中使用授权服务,有助于隐藏当前用户在给定上下文中不得接触到的 UI 元素。但请注意,光在视图中隐藏选项还不够。始终还需要在控制器中强制执行策略。
自定义要求
常备要求基本上涵盖了声明、身份验证,并提供了常规用途机制,用于根据断言进行自定义,但也可以创建自定义要求。
策略要求由以下两种元素组成:仅保留数据的要求类,以及对用户验证数据的授权处理程序。创建自定义要求,还可以进一步表达特定策略。例如,假设要将内容编辑者策略扩展为,增添用户至少必须有三年经验的要求。具体代码如下:
publicclassExperienceRequirement : IAuthorizationRequirement{ publicintYears { get; privateset; } publicExperienceRequirement(intminimumYears) { Years = minimumYears; }}
要求至少必须有一个授权处理程序。处理程序的类型为 AuthorizationHandler,其中 T 是要求类型。图 3 展示了 ExperienceRequirement 类型的示例处理程序。
图 3:示例授权处理程序
publicclassExperienceHandler : AuthorizationHandler{ protectedoverrideTask HandleRequirementAsync( AuthorizationHandlerContext context, ExperienceRequirement requirement) { // Save User object to access claimsvaruser = context.User; if(!user.HasClaim(c => c.Type == "EditorSince")) returnTask.CompletedTask; varsince = user.FindFirst("EditorSince").Value.ToInt(); if(since >= requirement.Years) context.Succeed(requirement); returnTask.CompletedTask; }}
示例授权处理程序读取与用户关联的声明,并检查自定义 EditorSince 声明。如果找不到,处理程序便无法返回成功。只有在找到声明且包含的整数值不低于指定年数时,才能返回成功。
自定义声明应为一条信息,以某种方式与保存到身份验证 Cookie 中的用户相关联(例如,“用户”表中的列)。
不过,一旦保留对用户的引用,便始终可以从声明中找到用户名,并对任何数据库或外部服务运行查询,以获取经验年数,从而在处理程序中使用此信息。(我承认,如果 EditorSince 值保留 DateTime,并计算用户担任编辑者是否已有一定年数,此示例会更真实一点。)
授权处理程序调用方法 Succeed,同时传递当前要求,以通知此要求已成功得到验证。如果没有传递要求,处理程序无需执行任何操作,可以直接返回内容。不过,如果处理程序要确定是否不符合要求(无论其他处理程序是否已成功验证同一要求),将会对授权上下文对象调用方法 Fail。
下面展示了如何将自定义要求添加到策略(请注意,由于这是自定义要求,因此没有扩展方法,而必须继续处理策略对象的整个 Requirements 集合):
services.AddAuthorization(options =>{ options.AddPolicy("AtLeast3Years", policy => policy .Requirements .Add(newExperienceRequirement(3)));});
此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
services.AddSingleton c.Type == "EditorSince")) returnTask.CompletedTask; varsince = user.FindFirst("EditorSince").Value.ToInt(); if(since >= requirement.Years) context.Succeed(requirement); returnTask.CompletedTask; }}
示例授权处理程序读取与用户关联的声明,并检查自定义 EditorSince 声明。如果找不到,处理程序便无法返回成功。只有在找到声明且包含的整数值不低于指定年数时,才能返回成功。
自定义声明应为一条信息,以某种方式与保存到身份验证 Cookie 中的用户相关联(例如,“用户”表中的列)。
不过,一旦保留对用户的引用,便始终可以从声明中找到用户名ASPCMS批量更新文章,并对任何数据库或外部服务运行查询,以获取经验年数,从而在处理程序中使用此信息。(我承认,如果 EditorSince 值保留 DateTime,并计算用户担任编辑者是否已有一定年数,此示例会更真实一点。)
授权处理程序调用方法 Succeed,同时传递当前要求,以通知此要求已成功得到验证。如果没有传递要求,处理程序无需执行任何操作,可以直接返回内容。不过,如果处理程序要确定是否不符合要求(无论其他处理程序是否已成功验证同一要求),将会对授权上下文对象调用方法 Fail。
下面展示了如何将自定义要求添加到策略(请注意,由于这是自定义要求,因此没有扩展方法,而必须继续处理策略对象的整个 Requirements 集合):
services.AddAuthorization(options =>{ options.AddPolicy("AtLeast3Years", policy => policy .Requirements .Add(newExperienceRequirement(3)));});
此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
services.AddSingleton= requirement.Years) context.Succeed(requirement); returnTask.CompletedTask; }}
示例授权处理程序读取与用户关联的声明,并检查自定义 EditorSince 声明。如果找不到,处理程序便无法返回成功。只有在找到声明且包含的整数值不低于指定年数时,才能返回成功。
自定义声明应为一条信息,以某种方式与保存到身份验证 Cookie 中的用户相关联(例如,“用户”表中的列)。
不过,一旦保留对用户的引用,便始终可以从声明中找到用户名,并对任何数据库或外部服务运行查询,以获取经验年数,从而在处理程序中使用此信息。(我承认,如果 EditorSince 值保留 DateTime,并计算用户担任编辑者是否已有一定年数,此示例会更真实一点。)
授权处理程序调用方法 Succeed,同时传递当前要求,以通知此要求已成功得到验证。如果没有传递要求,处理程序无需执行任何操作,可以直接返回内容。不过,如果处理程序要确定是否不符合要求(无论其他处理程序是否已成功验证同一要求),将会对授权上下文对象调用方法 Fail。
下面展示了如何将自定义要求添加到策略(请注意,由于这是自定义要求,因此没有扩展方法,而必须继续处理策略对象的整个 Requirements 集合):
services.AddAuthorization(options =>{ options.AddPolicy("AtLeast3Years", policy => policy .Requirements .Add(newExperienceRequirement(3)));});
此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
ASPCMS批量添加栏目services.AddSingleton{ options.AddPolicy("AtLeast3Years", policy => policy .Requirements .Add(newExperienceRequirement(3)));});
此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
services.AddSingleton policy .Requirements .Add(newExperienceRequirement(3)));});
此外,还需要在 IAuthorizationHandler 类型的范围内向 DI 系统注册新的处理程序:
services.AddSingleton();
如前所述,要求可包含多个处理程序。如果为授权层的同一要求向 DI 系统注册多个处理程序,有一个成功就足够了。
访问当前 HttpContext
在实现授权处理程序的过程中,可能需要检查请求属性或路由数据,如下所示:
if(context.Resource isAuthorizationFilterContext mvc){ varurl = mvc.HttpContext.Request.GetDisplayUrl(); ...}
在 ASP.NET Core 中,AuthorizationHandlerContext 对象向 FilterContext 对象公开 Resource 属性集。上下文对象因所涉及的框架而异。例如,MVC 和 SignalR 发送自己的特定对象。是否发生转换视需要访问的内容而定。例如,用户信息始终可用,所以无需为此进行转换;但若要获取 MVC 专属详细信息(如路由信息),则需要进行转换。
总结
在 ASP.NET Core 中,授权分为两种。一种是基于角色的传统授权,它的工作原理与在经典 ASP.NET MVC 中的工作原理相同,但仍存在相当平面化的结构限制,不适合表达复杂的授权逻辑。
基于策略的身份验证是一种新方法,可提供更丰富、更易表达的模型。这是因为,策略包含一系列基于声明的要求,以及基于可从 HTTP 上下文或外部源注入的其他任何信息的自定义逻辑。这些要求各自与一个或多个处理程序相关联,这些处理程序负责要求的实际计算。
Dino Esposito是《Microsoft .NET:构建面向企业的应用程序》(Microsoft Press,2014 年)和《使用 ASP.NET 构建新型 Web 应用程序》(Microsoft Press,2016 年)的作者。作为 JetBrains 的 .NET 和 Android 平台的技术推广人员,Esposito 经常在全球行业活动中发表演讲,并在 software2cents@wordpress.com 上以及 Twitter @despos 上的推文中分享他对于软件的愿景。
文章地址:https://www.tianxianmao.com/article/other/clsjsyxjswnrbjzhgjyhdyh.html