码云仓库源码地址 https://github.com/whuanles/2020-07-12

ASP.NET Core 中的策略授权

首先我们来创建一个 WebAPI 应用。

然后引入 Microsoft.AspNetCore.Authentication.JwtBearer 包。

策略

Startup 类的 ConfigureServices 方法中,添加一个策略的形式如下:

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

这里我们分步来说。

services.AddAuthorization 用于添加授权方式,目前只支持 AddPolicy。

ASP.NET Core 中,有基于角色、声明、策略的三种授权形式,都是使用 AddPolicy 来添加授权处理。

其中,有两个 API 如下:

  1. public void AddPolicy(string name, AuthorizationPolicy policy);
  2. public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy);

name = "AtLeast21",这里 "AtLeast21" 是策略的名称。

policy.Requirements.Add() 用于添加一个策略的标记(存储此策略的数据),此标记需要继承 IAuthorizationRequirement 接口。

策略的名称应该如何设置呢?在授权上应该如何编写策略以及使用 Requirements.Add()

这里先放一放,我们接下来再讲解。

定义一个 Controller

我们来添加一个 Controller :

  1. [ApiController]
  2. [Route("[controller]")]
  3. public class BookController : ControllerBase
  4. {
  5. private static List<string> BookContent = new List<string>();
  6. [HttpGet("Add")]
  7. public string AddContent(string body)
  8. {
  9. BookContent.Add(body);
  10. return "success";
  11. }
  12. [HttpGet("Remove")]
  13. public string RemoveContent(int n)
  14. {
  15. BookContent.Remove(BookContent[n]);
  16. return "success";
  17. }
  18. [HttpGet("Select")]
  19. public List<object> SelectContent()
  20. {
  21. List<object> obj = new List<object>();
  22. int i = 0;
  23. foreach (var item in BookContent)
  24. {
  25. int tmp = i;
  26. i++;
  27. obj.Add(new { Num = tmp, Body = item });
  28. }
  29. return obj;
  30. }
  31. [HttpGet("Update")]
  32. public string UpdateContent(int n, string body)
  33. {
  34. BookContent[n] = body;
  35. return "success";
  36. }
  37. }

功能很简单,就是对列表内容增删查改。

设定权限

前面我们创建了 BookController ,具有增删查改的功能。应该为每一个功能都应该设置一种权限。

ASP.NET Core 中,一个权限标记,需要继承IAuthorizationRequirement 接口。

我们来设置五个权限:

添加一个文件,填写以下代码。

  1. /*
  2. IAuthorizationRequirement 是一个空接口,具体对于授权的需求,其属性等信息是自定义的
  3. 这里的继承关系也没有任何意义
  4. */
  5. // 能够访问 Book 的权限
  6. public class BookRequirment : IAuthorizationRequirement
  7. {
  8. }
  9. // 增删查改 Book 权限
  10. // 可以继承 IAuthorizationRequirement ,也可以继承 BookRequirment
  11. public class BookAddRequirment : BookRequirment
  12. {
  13. }
  14. public class BookRemoveRequirment : BookRequirment
  15. {
  16. }
  17. public class BookSelectRequirment : BookRequirment
  18. {
  19. }
  20. public class BookUpdateRequirment : BookRequirment
  21. {
  22. }

BookRequirment 代表能够访问 BookController,其它四个分别代表增删查改的权限。

定义策略

权限设定后,我们开始设置策略。

在 Startup 的 ConfigureServices 中,添加:

  1. services.AddAuthorization(options =>
  2. {
  3. options.AddPolicy("Book", policy =>
  4. {
  5. policy.Requirements.Add(new BookRequirment());
  6. });
  7. options.AddPolicy("Book:Add", policy =>
  8. {
  9. policy.Requirements.Add(new BookAddRequirment());
  10. });
  11. options.AddPolicy("Book:Remove", policy =>
  12. {
  13. policy.Requirements.Add(new BookRemoveRequirment());
  14. });
  15. options.AddPolicy("Book:Select", policy =>
  16. {
  17. policy.Requirements.Add(new BookSelectRequirment());
  18. });
  19. options.AddPolicy("Book:Update", policy =>
  20. {
  21. policy.Requirements.Add(new BookUpdateRequirment());
  22. });
  23. });

这里我们为每种策略只设置一种权限,当然每种策略都可以添加多个权限,

这里名称使用 : 隔开,主要是为了可读性,让人一看就知道是层次关系。

存储用户信息

这里为了更加简单,就不使用数据库了。

以下用户信息结构是随便写的。用户-角色-角色具有的权限。

这个权限用什么类型存储都可以。只要能够标识区分是哪个权限就行。

  1. /// <summary>
  2. /// 存储用户信息
  3. /// </summary>
  4. public static class UsersData
  5. {
  6. public static readonly List<User> Users = new List<User>();
  7. static UsersData()
  8. {
  9. // 添加一个管理员
  10. Users.Add(new User
  11. {
  12. Name = "admin",
  13. Email = "admin@admin.com",
  14. Role = new Role
  15. {
  16. Requirements = new List<Type>
  17. {
  18. typeof( BookRequirment),
  19. typeof( BookAddRequirment),
  20. typeof( BookRemoveRequirment),
  21. typeof( BookSelectRequirment),
  22. typeof( BookUpdateRequirment)
  23. }
  24. }
  25. });
  26. // 没有删除权限
  27. Users.Add(new User
  28. {
  29. Name = "作者",
  30. Email = "wirter",
  31. Role = new Role
  32. {
  33. Requirements = new List<Type>
  34. {
  35. typeof( BookRequirment),
  36. typeof( BookAddRequirment),
  37. typeof( BookRemoveRequirment),
  38. typeof( BookSelectRequirment),
  39. }
  40. }
  41. });
  42. }
  43. }
  44. public class User
  45. {
  46. public string Name { get; set; }
  47. public string Email { get; set; }
  48. public Role Role { get; set; }
  49. }
  50. /// <summary>
  51. /// 这里的存储角色的策略授权,字符串数字等都行,只要能够存储表示就OK
  52. /// <para>在这里没有任何意义,只是标识的一种方式</param>
  53. /// </summary>
  54. public class Role
  55. {
  56. public List<Type> Requirements { get; set; }
  57. }

标记访问权限

定义策略完毕后,就要为 Controller 和 Action 标记访问权限了。

使用 [Authorize(Policy = "{string}")] 特性和属性来设置访问此 Controller 、 Action 所需要的权限。

这里我们分开设置,每个功能标记一种权限(最小粒度应该是一个功能 ,而不是一个 API)。

  1. [Authorize(Policy = "Book")]
  2. [ApiController]
  3. [Route("[controller]")]
  4. public class BookController : ControllerBase
  5. {
  6. private static List<string> BookContent = new List<string>();
  7. [Authorize(Policy = "Book:Add")]
  8. [HttpGet("Add")]
  9. public string AddContent(string body){}
  10. [Authorize(Policy = "Book:Remove")]
  11. [HttpGet("Remove")]
  12. public string RemoveContent(int n){}
  13. [Authorize(Policy = "Book:Select")]
  14. [HttpGet("Select")]
  15. public List<object> SelectContent(){}
  16. [Authorize(Policy = "Book:Update")]
  17. [HttpGet("Update")]
  18. public string UpdateContent(int n, string body){}
  19. }

认证:Token 凭据

因为使用的是 WebAPI,所以使用 Bearer Token 认证,当然使用 Cookie 等也可以。使用什么认证方式都可以。

  1. // 设置验证方式为 Bearer Token
  2. // 添加 using Microsoft.AspNetCore.Authentication.JwtBearer;
  3. // 你也可以使用 字符串 "Brearer" 代替 JwtBearerDefaults.AuthenticationScheme
  4. services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  5. .AddJwtBearer(options =>
  6. {
  7. options.TokenValidationParameters = new TokenValidationParameters
  8. {
  9. ValidateIssuerSigningKey = true,
  10. IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234")), // 加密解密Token的密钥
  11. // 是否验证发布者
  12. ValidateIssuer = true,
  13. // 发布者名称
  14. ValidIssuer = "server",
  15. // 是否验证订阅者
  16. // 订阅者名称
  17. ValidateAudience = true,
  18. ValidAudience = "client007",
  19. // 是否验证令牌有效期
  20. ValidateLifetime = true,
  21. // 每次颁发令牌,令牌有效时间
  22. ClockSkew = TimeSpan.FromMinutes(120)
  23. };
  24. });

上面的代码是一个模板,可以随便改。这里的认证方式跟我们的策略授权没什么关系。

颁发登录凭据

下面这个 Action 放置到 BookController,作为登录功能。这一部分也不重要,主要是为用户颁发凭据,以及标识用户。用户的 Claim 可以存储此用户的唯一标识。

  1. /// <summary>
  2. /// 用户登录并且颁发凭据
  3. /// </summary>
  4. /// <param name="name"></param>
  5. /// <returns></returns>
  6. [AllowAnonymous]
  7. [HttpGet("Token")]
  8. public string Token(string name)
  9. {
  10. User user = UsersData.Users.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
  11. if (user is null)
  12. return "未找到此用户";
  13. // 定义用户信息
  14. var claims = new Claim[]
  15. {
  16. new Claim(ClaimTypes.Name, name),
  17. new Claim(JwtRegisteredClaimNames.Email, user.Email)
  18. };
  19. // 和 Startup 中的配置一致
  20. SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("abcdABCD1234abcdABCD1234"));
  21. JwtSecurityToken token = new JwtSecurityToken(
  22. issuer: "server",
  23. audience: "client007",
  24. claims: claims,
  25. notBefore: DateTime.Now,
  26. expires: DateTime.Now.AddMinutes(30),
  27. signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
  28. );
  29. string jwtToken = new JwtSecurityTokenHandler().WriteToken(token);
  30. return jwtToken;
  31. }

Configure 中补充以下两行:

  1. app.UseAuthentication();
  2. app.UseAuthorization();

自定义授权

自定义授权需要继承 IAuthorizationHandler 接口,实现此接口的类能够决定是否对用户的访问进行授权。

实现代码如下:

  1. /// <summary>
  2. /// 判断用户是否具有权限
  3. /// </summary>
  4. public class PermissionHandler : IAuthorizationHandler
  5. {
  6. public async Task HandleAsync(AuthorizationHandlerContext context)
  7. {
  8. // 当前访问 Controller/Action 所需要的权限(策略授权)
  9. IAuthorizationRequirement[] pendingRequirements = context.PendingRequirements.ToArray();
  10. // 取出用户信息
  11. IEnumerable<Claim> claims = context.User?.Claims;
  12. // 未登录或者取不到用户信息
  13. if (claims is null)
  14. {
  15. context.Fail();
  16. return;
  17. }
  18. // 取出用户名
  19. Claim userName = claims.FirstOrDefault(x => x.Type == ClaimTypes.Name);
  20. if (userName is null)
  21. {
  22. context.Fail();
  23. return;
  24. }
  25. // ... 省略一些检验过程 ...
  26. // 获取此用户的信息
  27. User user = UsersData.Users.FirstOrDefault(x => x.Name.Equals(userName.Value, StringComparison.OrdinalIgnoreCase));
  28. List<Type> auths = user.Role.Requirements;
  29. // 逐个检查
  30. foreach (IAuthorizationRequirement requirement in pendingRequirements)
  31. {
  32. // 如果用户权限列表中没有找到此权限的话
  33. if (!auths.Any(x => x == requirement.GetType()))
  34. context.Fail();
  35. context.Succeed(requirement);
  36. }
  37. await Task.CompletedTask;
  38. }
  39. }

过程:

  • 从上下文(Context) 中获取用户信息(context.User)
  • 获取此用户所属的角色,并获取此角色具有的权限
  • 获取此次请求的 Controller/Action 需要的权限(context.PendingRequirements)
  • 检查所需要的权限(foreach循环),此用户是否都具有

最后需要将此接口、服务,注册到容器中:

  1. services.AddSingleton<IAuthorizationHandler, PermissionHandler>();

做完这些后,就可以测试授权了。

IAuthorizationService

前面实现了 IAuthorizationHandler 接口的类,用于自定义确定用户是否有权访问此 Controller/Action。

IAuthorizationService 接口用于确定授权是否成功,其定义如下:

  1. public interface IAuthorizationService
  2. {
  3. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable<IAuthorizationRequirement> requirements);
  4. Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName);
  5. }

DefaultAuthorizationService 接口实现了 IAuthorizationService ,ASP.NET Core 默认使用 DefaultAuthorizationService 来确认授权。

前面我们使用 IAuthorizationHandler 接口来自定义授权,如果再深入一层的话,就追溯到了IAuthorizationService

DefaultAuthorizationService IAuthorizationService 的默认实现,其中有一段代码如下:

DefaultAuthorizationService 比较复杂,一般情况下,我们只要实现 IAuthorizationHandler ` 就够了。

参考资料:https://docs.microsoft.com/zh-cn/dotnet/api/microsoft.aspnetcore.authorization.defaultauthorizationservice?view=aspnetcore-3.1

ABP 授权

前面已经介绍了 ASP.NET Core 中的策略授权,这里介绍一下 ABP 中的授权,我们继续利用前面已经实现的 ASP.NET Core 代码。

创建 ABP 应用

Nuget 安装 Volo.Abp.AspNetCore.MvcVolo.Abp.Autofac

创建 AppModule 类,代码如下:

  1. [DependsOn(typeof(AbpAspNetCoreMvcModule))]
  2. [DependsOn(typeof(AbpAutofacModule))]
  3. public class AppModule : AbpModule
  4. {
  5. public override void OnApplicationInitialization(
  6. ApplicationInitializationContext context)
  7. {
  8. var app = context.GetApplicationBuilder();
  9. var env = context.GetEnvironment();
  10. if (env.IsDevelopment())
  11. {
  12. app.UseDeveloperExceptionPage();
  13. }
  14. else
  15. {
  16. app.UseExceptionHandler("/Error");
  17. }
  18. app.UseStaticFiles();
  19. app.UseRouting();
  20. app.UseConfiguredEndpoints();
  21. }
  22. }

在 Program 的 Host 加上 .UseServiceProviderFactory(new AutofacServiceProviderFactory()),示例如下:

  1. public static IHostBuilder CreateHostBuilder(string[] args) =>
  2. Host.CreateDefaultBuilder(args)
  3. .UseServiceProviderFactory(new AutofacServiceProviderFactory())
  4. ...
  5. ...

然后在 Startup 中的 ConfiguraServices 方法中,添加 ABP 模块, 并且设置使用 Autofac。

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddApplication<AppModule>(options=>
  4. {
  5. options.UseAutofac();
  6. });
  7. }

定义权限

ABP 中使用 PermissionDefinitionProvider 类来定义权限,创建一个类,其代码如下:

  1. public class BookPermissionDefinitionProvider : PermissionDefinitionProvider
  2. {
  3. public override void Define(IPermissionDefinitionContext context)
  4. {
  5. var myGroup = context.AddGroup("Book");
  6. var permission = myGroup.AddPermission("Book");
  7. permission.AddChild("Book:Add");
  8. permission.AddChild("Book:Remove");
  9. permission.AddChild("Book:Select");
  10. permission.AddChild("Book:Update");
  11. }
  12. }

这里定义了一个组 Book,定义了一个权限 Book了,Book 其下有四个子权限。

删除 Startup 中的services.AddAuthorization(options =>...

将剩余的依赖注入服务代码移动到 AppModule 的 ConfigureServices 中。

Startup 的 Configure 改成:

  1. app.InitializeApplication();

AbpModule 中的 Configure 改成:

  1. var app = context.GetApplicationBuilder();
  2. var env = context.GetEnvironment();
  3. if (env.IsDevelopment())
  4. {
  5. app.UseDeveloperExceptionPage();
  6. }
  7. else
  8. {
  9. app.UseExceptionHandler("/Error");
  10. }
  11. app.UseStaticFiles();
  12. app.UseRouting();
  13. app.UseAuthentication();
  14. app.UseAuthorization();
  15. app.UseConfiguredEndpoints();

PermissionHandler 需要改成:

  1. public class PermissionHandler : IAuthorizationHandler
  2. {
  3. public Task HandleAsync(AuthorizationHandlerContext context)
  4. {
  5. // 当前访问 Controller/Action 所需要的权限(策略授权)
  6. IAuthorizationRequirement[] pendingRequirements = context.PendingRequirements.ToArray();
  7. // 逐个检查
  8. foreach (IAuthorizationRequirement requirement in pendingRequirements)
  9. {
  10. context.Succeed(requirement);
  11. }
  12. return Task.CompletedTask;
  13. }
  14. }

删除 UserData 文件;BookController 需要修改一下登录和凭证。

具体细节可参考仓库源码。

ASP.NET Core策略授权和 ABP 授权的更多相关文章

  1. 【ASP.NET Core】运行原理(4):授权

    本系列将分析ASP.NET Core运行原理 [ASP.NET Core]运行原理(1):创建WebHost [ASP.NET Core]运行原理(2):启动WebHost [ASP.NET Core ...

  2. [译]基于ASP.NET Core 3.0的ABP v0.21已发布

    基于ASP.NET Core 3.0的ABP v0.21已发布 在微软发布仅仅一个小时后, 基于ASP.NET Core 3.0的ABP v0.21也紧跟着发布了. v0.21没有新功能.它只是升级到 ...

  3. 使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)——第1部分

    原文:使用JWT的ASP.NET CORE令牌身份验证和授权(无Cookie)--第1部分 原文链接:https://www.codeproject.com/Articles/5160941/ASP- ...

  4. asp.net core策略授权

    在<asp.net core认证与授权>中讲解了固定和自定义角色授权系统权限,其实我们还可以通过其他方式来授权,比如可以通过角色组,用户名,生日等,但这些主要取决于ClaimTypes,其 ...

  5. asp.net Core 中AuthorizationHandler 实现自定义授权

    前言 ASP.NET Core 中 继承的是AuthorizationHandler ,而ASP.NET Framework 中继承的是AuthorizeAttribute. 它们都是用过重写里面的方 ...

  6. 第十五节:Asp.Net Core中的各种过滤器(授权、资源、操作、结果、异常)

    一. 简介 1. 说明 提到过滤器,通常是指请求处理管道中特定阶段之前或之后的代码,可以处理:授权.响应缓存(对请求管道进行短路,以便返回缓存的响应). 防盗链.本地化国际化等,过滤器用于横向处理业务 ...

  7. OAuth2.0授权和SSO授权

    一. OAuth2.0授权和SSO授 1. OAuth2.0 --> 网页 --> 当前程序内授权 --> 输入账号密码 --> (自己需要获取到令牌, 自己处理逻辑) 授权成 ...

  8. ASP.Net Core 2.1+ Cookie 登录授权验证【简单Cookie验证】

    介绍 本文章发布于博客园:https://www.cnblogs.com/fallstar/p/11310749.html 作者:fallstar 本文章适用于:ASP.NET Core 2.1 + ...

  9. asp.net core 控制静态文件的授权

    静态文件访问在网站中是一项重要的服务,用于向前端提供可以直接访问的文件,如js,css,文档等,方法是在Startup的Configure中添加UseStaticFiles()管道. 参考:ASP.N ...

随机推荐

  1. c printf(“%d”,变量)函数

  2. 网页中为什么常用AT替换@(repost from https://zhidao.baidu.com/question/122291.html)

    经常在个人主页上看到别人的邮箱地址中@被AT符号替代,很是迷惑,这样替代有什么好处呢?还是说html原有的原因使界面中不能出现@,查阅资料后解答如下: 写成AT [at],是为了防止被一些邮件扫描器搜 ...

  3. Mac App 破解之路八 病毒程序分析

    本人使用MacBooster 7 扫出了几个未知程序. JMJ56 这个程序. 在finder中打开发现是一个shell脚本 调用了python 9NKb0 就是python脚本使用.    只不过是 ...

  4. cino伟斯 A770键盘界面快速设定记录后缀删除添加换行回车操作方法

    http://www.cinoscan.com/upload/2016063033256485.pdf cino A770键盘界面快速设定记录后缀删除添加换行回车操作方法

  5. PHP丨PHP基础知识之条件语IF判断「理论篇」

    if语句是指编程语言(包括c语言.C#.VB.java.php.汇编语言等)中用来判定所给定的条件是否满足,根据判定的结果(真或假)决定执行给出的两种操作之一. if语句概述 if语句是指编程语言(包 ...

  6. Android安全初学笔记

    安全概述 安全主要解决4类问题 保密:不希望第三方窥探 鉴别:与你通信的人可以被确认 完整性:不能被随意篡改,或者能鉴别是否被篡改 不可否认性:能确认产生信息的人,并且产生该信息的人在何时都无法否认产 ...

  7. CPU明明8个核,网卡为啥拼命折腾一号核?

    中断机制 我是CPU一号车间的阿Q,我又来了! 我们日常的工作就是不断执行代码指令,不过这看似简单的工作背后其实也并不轻松. 咱不能闷着头啥也不管一个劲的只管执行代码,还得和连接在主板上的其他单位打交 ...

  8. AbstractQueuedSynchronizer和ReentranLock基本原理

    先把我主要学习参考的文章放上来先,这篇文章讲的挺好的,分析比较到位,最好是先看完这篇文章,在接下去看我写的.不然你会一脸懵逼,不过等你看完这篇文章,可能我的文章对你也用途不大了 深入分析Abstrac ...

  9. Bash 脚本编程的一些高级用法

    概述 偶然间发现 man bash 上其实详细讲解了 shell 编程的语法,包括一些很少用却很实用的高级语法.就像发现了宝藏的孩子,兴奋莫名.于是参考man bash,结合自己的理解,整理出了这篇文 ...

  10. 小程序拾色器(颜色选择器)组件mini-color-picker

    特性: 现有方案分析 we-color-picker 需注意组件定位,操作不跟手不流畅,配置复杂.其定位会撑开原有页面,体验不佳.滑动距离按像素区分(固定),需考虑设备分辨率,不利于多端. Papae ...