1.5 基于策略的授权

  在上篇中,已经讲到了授权访问(authorization)的四种方式。其中Razor Pages授权约定和简单授权二种方式更像是身份认证(authentication) ,因为只要是合法用户登录就能访问资源。 而角色授权和声明授权二种方式是真正的授权访问(authorization)。

  下面继续讲authorization的第五种方式--策略授权。策略授权由一个或多个需求(也可以称"要求")组成(需求:TRequirement)。它在程序启动时注册为授权服务配置的一部分。在ConfigureServices方法中注册。

  (1) 注册策略授权

    创建了一个名为"AtLeast21"的策略授权,这个策略的需求是最小年龄需求,策略通过参数对象(IAuthorizationRequirement)提供,它要求最低年龄是21岁。

public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddAuthorization(options =>
{
options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
// MinimumAgeRequirement参数对象 实现了IAuthorizationRequirement
options.AddPolicy("AtLeast21", policy => policy.Requirements.Add(new MinimumAgeRequirement()));
});
}

  

  (2) 策略授权应用到mvc的控制器或Razor Pages

//用户购买酒业务, 策略授权应用到控制器,要求用户年龄不能低于21岁
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseController : Controller
{
public IActionResult Index() => View();
}
//策略授权到razor pages的PageModel类
[Authorize(Policy = "AtLeast21")]
public class AlcoholPurchaseModel : PageModel
{
}

    在razor pages中,策略还可以应用到razor page授权约定中。

    public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath, string policy);

  (3) Requirement策略授权需求

    策略授权需求实现IAuthorizationRequirement接口,用于策略需求对象参数传递。MinimumAgeRequirement就是一个需求参数对象。

using Microsoft.AspNetCore.Authorization;
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; } public MinimumAgeRequirement(int minimumAge)
{
MinimumAge = minimumAge;
}
}

  (4) 策略授权处理程序类

    授权处理程序负责评估要求的属性(指策略授权逻辑处理,把当前用户的年龄与策略要求年龄进行验证)。 授权处理程序会针对提供的AuthorizationHandlerContext 来评估要求,确定是否允许访问或拒绝。

  实现策略授权处理程序,需要继承AuthorizationHandler<TRequirement>,其中TRequirement就是参数对象。另外,一个处理程序也可以通过实现 IAuthorizationHandler 来处理多个类型的要求。

    下面是一对一关系的示例(一个Handler处理一个TRequirement对象),评估最低年龄要求:

   public class MinimumAgeHandler: AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth &&
c.Issuer == "LOCAL AUTHORITY"))
{
//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 == "LOCAL AUTHORITY").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;
}
}

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

    

  (5) 处理程序注入到服务集合,采用单例

    services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

    在UserClaim用户声明表中,保存一条符合该策略授权的数据,当启动程序,访问AlcoholPurchase资源时,进入授权处理程序MinimumAgeHandler中, 执行context.Succeed(requirement)后, 授权成功。

    

  

  1.5.1 多个需求使用一个处理程序

    下面是多个需求(TRequirement)使用一个处理程序,Handler实现IAuthorizationHandler接口,下面示例是一个对多关系的权限处理程序,可以在其中处理三种不同类型的需求:

public class PermissionHandler : IAuthorizationHandler
{
public Task HandleAsync(AuthorizationHandlerContext context)
{
//获取策略中的多个需求,返回IEnumerable<IAuthorizationRequirement>类型
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;
}

    具体详细代码,查看官方示例, Github

  

  1.5.2 处理程序应返回什么? (有三种返回)

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

(2) 处理程序通常不需要处理失败(显示加context.Fail()),因为同一要求的其他处理程序(1.5.3)可能会成功。

(3) 若要保证授权失败,即使其它要求处理程序会成功,也会失败,请调用context.Fail();

    

  1.5.3  一个需求应用在多个处理程序

     这里授权处理正好与15.1相反,下面这个示例是门禁卡授权策略需求, 你公司的门禁卡丢在家中,去公司后要求前台给个临时门禁卡来开门。这种情况下,只有一个需求,但有多个处理程序,每个处理程序针对单个要求进行检查。

     //策略授权需求,这里没有参数需求, 参数是硬编码在处理程序中
   public class BuildingEntryRequirement : IAuthorizationRequirement
   {
   }
//门禁卡处理程序
public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
//需求参数硬编码 BadgeId
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;
}
}
//临时门禁卡处理程序
public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
BuildingEntryRequirement requirement)
{
//需求参数硬编码 TemporaryBadgeId
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;
}
}
      // 注册策略
   services.AddAuthorization(options =>
{
options.AddPolicy("BadgeEntry", policy =>policy.Requirements.Add(new BuildingEntryRequirement()));
   });
    // 注入服务
   services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
  services.AddSingleton<IAuthorizationHandler, TemporaryStickerHandler>();

    当[Authorize(Policy = " BadgeEntry ")]应用到控制器后,只要有一个处理程序成功,则策略授权成功。需要在UserClaim用户声明表中维护好ClaimType。

  1.5.4 使用 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")));
});

    

  总结:通过这二篇,熟悉了授权的五种方式,包括: 

    Razor Pages授权约定
    简单授权
    角色授权
    声明授权
    策略授权

    其中声明授权包含了角色授权,通过ClaimTypes.Role 可以在声明中使用角色授权。

   public const string Role = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"

 参考文献

    基于策略的授权

asp.net core系列 50 Identity 授权(中)的更多相关文章

  1. asp.net core系列 49 Identity 授权(上)

    一.概述 授权是指用户能够访问资源的权限,如页面数据的查看.编辑.新增.删除.导出.下载等权限.ASP.NET Core 授权提供了多种且灵活的方式,包括:Razor pages授权约定.简单授权.R ...

  2. asp.net core系列 51 Identity 授权(下)

    1.6 基于资源的授权 前面二篇中,熟悉了五种授权方式(对于上篇讲的策略授权,还有IAuthorizationPolicyProvider的自定义授权策略提供程序没有讲,后面再补充).本篇讲的授权方式 ...

  3. asp.net core系列 46 Identity介绍

    一. Identity 介绍 ASP.NET Core Identity是一个会员系统,可为ASP.NET Core应用程序添加登录功能.可以使用SQL Server数据库配置身份以存储用户名,密码和 ...

  4. asp.net core系列 52 Identity 其它关注点

    一.登录分析 在使用identity身份验证登录时,在login中调用的方法是: var result = await _signInManager.PasswordSignInAsync(Input ...

  5. asp.net core系列 48 Identity 身份模型自定义

    一.概述 ASP.NET Core Identity提供了一个框架,用于管理和存储在 ASP.NET Core 应用中的用户帐户. Identity添加到项目时单个用户帐户选择作为身份验证机制. 默认 ...

  6. asp.net core系列 47 Identity 自定义用户数据

    一.概述 接着上篇的WebAppIdentityDemo项目,将自定义用户数据添加到Identity DB,自定义扩展的用户数据类应继承IdentityUser类, 文件名为Areas / Ident ...

  7. 【目录】asp.net core系列篇

    随笔分类 - asp.net core系列篇 asp.net core系列 68 Filter管道过滤器 摘要: 一.概述 本篇详细了解一下asp.net core filters,filter叫&q ...

  8. Asp.net Core 系列之--5.认证、授权与自定义权限的实现

    ChuanGoing 2019-11-24 asp.net core系列已经来到了第五篇,通过之前的基础介绍,我们了解了事件订阅/发布的eventbus整个流程,初探dapper ORM实现,并且简单 ...

  9. WPF中的常用布局 栈的实现 一个关于素数的神奇性质 C# defualt关键字默认值用法 接口通俗理解 C# Json序列化和反序列化 ASP.NET CORE系列【五】webapi整理以及RESTful风格化

    WPF中的常用布局   一 写在开头1.1 写在开头微软是一家伟大的公司.评价一门技术的好坏得看具体的需求,没有哪门技术是面面俱到地好,应该抛弃对微软和微软的技术的偏见. 1.2 本文内容本文主要内容 ...

随机推荐

  1. Linux的动态库与静态库

    1.动态库与静态库简介 在实际的软件开发中,为了方便使用一些被重复调用的公共代码,我们经常将这些公共的函数编译成动态库或静态库.我们知道程序一般要经过预处理.编译.汇编和链接这几个步骤才能变成可执行的 ...

  2. 在Windows上安装FFmpeg程序的方法(you-get下载视频必备程序)

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.它提供了录制.转换以及流化音视频的完整解决方案.它包含了非常先进的音频/视频编解码库libavcodec. 该程序 ...

  3. SOFA 源码分析 — 预热权重

    前言 SOFA-RPC 支持根据权重对服务进行预热功能,具体地址:预热权重. 引用官方文档: 预热权重功能让客户端机器能够根据服务端的相应权重进行流量的分发.该功能也常被用于集群内少数机器的启动场景. ...

  4. 2018 CISCN reverse wp

    2018 CISCN reverse wp 这题比赛的时候没做出来,主要是心态崩了看不下去..赛后看了下网上的wp发现不难,是自己想复杂了.这里将我的思路和exp放出来,希望大家一起交流学习. mai ...

  5. java 字符常量池

    一.题目: 问题:String str = new String(“hello”),“hello”在内存中是怎么分配的?    答案是:堆,字符串常量区. Java中的字符串常量池和JVM运行时数据区 ...

  6. linux中普通的文件查看操作(cat、more、less、head、tail)

    cat:基本是最常用的查看文件内容的linux命令. more 也是用来查看一个文件的内容.当文件内容太多,一屏幕不能占下,而你用cat肯定是看不前面的内容的,那么使用more就可以解决这个问题了.当 ...

  7. Netty中解码基于分隔符的协议和基于长度的协议

    在使用Netty的过程中,你将会遇到需要解码器的基于分隔符和帧长度的协议.本节将解释Netty所提供的用于处理这些场景的实现. 基于分隔符的协议 基于分隔符的(delimited)消息协议使用定义的字 ...

  8. http响应结构分析

    HTTP响应由三个部分组成: 1.状态码(Status Code): 描述了响应的状态.可以用来检查是否成功的完成了请求.请求失败的情况下,状态码可用来找出失败的原因.如果Servlet没有返回状态码 ...

  9. 如何在js或者jquery中操作EL表达式的一个List集合

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 先说明此篇博客看明白了可以干嘛: 就是在js或者jquery中操作一个EL表达式的一个list集合或者复杂类型 ...

  10. client,server,nginx 在使用keepAlive 专题

    2. TCP keepalive overview In order to understand what TCP keepalive (which we will just call keepali ...