ASP.NET Core 认证与授权[7]:动态授权

基于资源的授权

有些场景下,授权需要依赖于要访问的资源,例如:每个资源通常会有一个创建者属性,我们只允许该资源的创建者才可以对其进行编辑,删除等操作,这就无法通过[Authorize]特性来指定授权了。因为授权过滤器会在我们的应用代码,以及MVC的模型绑定之前执行,无法确定所访问的资源。此时,我们需要使用基于资源的授权,下面就来演示一下具体是如何操作的。

定义资源Requirement

在基于资源的授权中,我们要判断的是用户是否具有针对该资源的某项操作,因此,我们先定义一个代表操作的Requirement

public class MyRequirement : IAuthorizationRequirement{  
 public string Name { get; set; } }

可以根据实际场景来定义需要的属性,在本示例中,只需要一个Name属性,用来表示针对资源的操作名称(如:增查改删等)。

然后,我们预定义一些常用的操作,方便业务中的调用:

public static class Operations{    
   public static MyRequirement Create = new MyRequirement { Name = "Create" };    

public static MyRequirement Read = new MyRequirement { Name = "Read" };    
public static MyRequirement Update = new MyRequirement { Name = "Update" };  
public static MyRequirement Delete = new MyRequirement { Name = "Delete" }; }

上面定义的 MyRequirement 虽然很简单,但是非常通用,因此,在 ASP.NET Core 中也内置了一个OperationAuthorizationRequirement

public class OperationAuthorizationRequirement : IAuthorizationRequirement{    public string Name { get; set; }
}

在实际应用中,我们可以直接使用OperationAuthorizationRequirement,而不需要再自定义 Requirement,而在这里只是为了方便理解,后续也继续使用 MyRequirement 来演示。

实现资源授权Handler

每一个 Requirement 都需要有一个对应的 Handler,来完成授权逻辑,可以直接让 Requirement 实现IAuthorizationHandler接口,也可以单独定义授权Handler,在这里使用后者。

在本示例中,我们是根据资源的创建者来判断用户是否具有操作权限,因此,我们定义一个资源创建者的接口,而不是直接依赖于具体的资源:

public interface IDocument{    string Creator { get; set; }
}

然后实现我们的授权Handler:

public class DocumentAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, IDocument>
{    
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement, IDocument resource)    {        // 如果是Admin角色就直接授权成功if (context.User.IsInRole("admin")){context.Succeed(requirement);}        else{            // 允许任何人创建或读取资源if (requirement == Operations.Create || requirement == Operations.Read){context.Succeed(requirement);}          
 else{                // 只有资源的创建者才可以修改和删除if (context.User.Identity.Name == resource.Creator){context.Succeed(requirement);}            
    else{context.Fail();}}}        return Task.CompletedTask;} }

在前面章节的《自定义策略》示例中,我们继承的是AuthorizationHandler<NameAuthorizationRequirement>,而这里继承了AuthorizationHandler<OperationAuthorizationRequirement, Document>,很明显,比之前的多了resource参数,以便用来实现基于资源的授权。

如上,我们并没有验证用户是否已登录,以及context.User是否为空等。这是因为在 ASP.NET Core 的默认授权中,已经对这些进行了判断,我们只需要在要授权的控制器上添加[Authorize]特性即可,无需重复性的工作。

最后,不要忘了,还需要将DocumentAuthorizationHandler注册到DI系统中:

services.AddSingleton<IAuthorizationHandler, DocumentAuthorizationHandler>();

调用AuthorizationService

现在就可以在我们的应用代码中调用IAuthorizationService来完成授权了,不过在此之前,我们再来回顾一下IAuthorizationService接口:

public interface IAuthorizationService{   

 Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);  
  Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName); }

在《上一章》中,我们提到,使用[Authorize]设置授权时,其AuthorizationHandlerContext中的resource字段被设置为空,现在,我们将要授权的资源传进去即可:

[Authorize]
public class DocumentsController : Controller{  

 public async Task<ActionResult> Details(int? id)    {    
     var document = _docStore.Find(id.Value);    
      if (document == null){        
         return NotFound();}      
      if ((await _authorizationService.AuthorizeAsync(User, document, Operations.Read)).Succeeded){          
        return View(document);}        
      else{        
          return new ForbidResult();}}    

public async Task<IActionResult> Edit(int? id)    {    
   var document = _docStore.Find(id.Value);    
      if (document == null){          
        return NotFound();}      
       if ((await _authorizationService.AuthorizeAsync(User, document, Operations.Update)).Succeeded){        
        return View(document);}      
       else{        
           return new ForbidReuslt();}} }

如上,在授权失败时,我们返回了ForbidResult,建议不要返回ChallengeResult,因为我们要明确的告诉用户是无权访问,而不是未登录。

基于资源的权限非常简单,但是每次都要在应用代码中显示调用IAuthorizationService,显然比较繁琐,我们也可以使用AOP模式,或者使用EF Core拦截器来实现,将授权验证与业务代码分离。

基于权限的授权

在一个通用的用户权限管理系统中,通常每一个Action都代表一种权限,用户拥有哪些权限也是可以动态分配的。本小节就来介绍一下在 ASP.NET Core 中,如何实现一个简单权限管理系统。

定义权限项

首先,我们要确定我们的系统分为哪些权限项,这通常是由业务所决定的,并且是预先确定的,我们可以硬编码在代码中,方便统一调用:

public static class Permissions{    
public const string User = "User";  
 public const string UserCreate = "User.Create";  
   public const string UserRead = "User.Read";  
     public const string UserUpdate = "User.Update";  
       public const string UserDelete = "User.Delete"; }

如上,我们简单定义了“创建用户”,“查询用户”,“更新用户”,“删除用户”四个权限。通常会对权限项进行分组,构成一个树形结构,这样在展示和配置权限时,都会方便很多。在这里,使用.来表示层级进行分组,其中User权限项包含所有以User.开头的权限。

定义权限Requirement

与基于资源的授权类似,我们同样需要定义一个权限Requirement

public class PermissionAuthorizationRequirement : IAuthorizationRequirement{    public PermissionAuthorizationRequirement(string name)    {Name = name;}    public string Name { get; set; }
}

使用Name属性来表示权限的名称,与上面Permissions的常量对应。

实现权限授权Handler

然后实现与上面定义的 Requirement 对应的授权Handler:

public class PermissionAuthorizationHandler : AuthorizationHandler<PermissionAuthorizationRequirement>
{   
 private readonly UserStore _userStore;  
 
  public PermissionAuthorizationHandler(UserStore userStore)    {_userStore = userStore;}  
  
  protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement)    {    
      if (context.User != null){            
      if (context.User.IsInRole("admin")){context.Succeed(requirement);}        
         else{              
           var userIdClaim = context.User.FindFirst(_ => _.Type == ClaimTypes.NameIdentifier);              
             if (userIdClaim != null){              
                  if (_userStore.CheckPermission(int.Parse(userIdClaim.Value), requirement.Name)){context.Succeed(requirement);}}}}        
     return Task.CompletedTask;} }

如上,把admin角色设置为内部固定角色,直接跳过授权检查。其他角色则从Claims中取出用户Id,然后调用CheckPermission完成授权。

权限检查的具体逻辑就属于业务层面的了,通常会从数据库中查找用的的权限列表进行验证,这里就不在多说,简单模拟了一下:

public class UserStore{   
 private static List<User> _users = new List<User>() {    
     new User {  Id=1, Name="admin", Password="111111", Role="admin", Email="admin@gmail.com", PhoneNumber="18800000000"},      
       new User {  Id=2, Name="alice", Password="111111", Role="user", Email="alice@gmail.com", PhoneNumber="18800000001", Permissions = new List<UserPermission> {              
        new UserPermission { UserId = 1, PermissionName = Permissions.User },                
        new UserPermission { UserId = 1, PermissionName = Permissions.Role }}},      
         new User {  Id=3, Name="bob", Password="111111", Role = "user", Email="bob@gmail.com", PhoneNumber="18800000002", Permissions = new List<UserPermission> {            
          new UserPermission { UserId = 2, PermissionName = Permissions.UserRead },              
          new UserPermission { UserId = 2, PermissionName = Permissions.RoleRead }}},};    

public bool CheckPermission(int userId, string permissionName)    {        var user = Find(userId);      
 if (user == null) return false;    
     return user.Permissions.Any(p => permissionName.StartsWith(p.PermissionName));} }

最后,与上面示例一样,将Handler注册到DI系统中:

services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

使用策略授权

那么,怎么在应用代码中使用基于权限的授权呢?

最为简单的,我们可以直接借助于 ASP.NET Core 的授权策略来实现基于权限的授权,因为此时并不需要资源。

services.AddAuthorization(options =>
{options.AddPolicy(Permissions.UserCreate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserCreate)));options.AddPolicy(Permissions.UserRead, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserRead)));options.AddPolicy(Permissions.UserUpdate, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserUpdate)));options.AddPolicy(Permissions.UserDelete, policy => policy.AddRequirements(new PermissionAuthorizationRequirement(Permissions.UserDelete)));
});

如上,针对每一个权限项都定义一个对应的授权策略,然后,就可以在控制器中直接使用[Authorize]来完成授权:

[Authorize]public class UserController : Controller{[Authorize(Policy = Permissions.UserRead)]   
 public ActionResult Index()    {}[Authorize(Policy = Permissions.UserRead)]  
 public ActionResult Details(int? id)    {}[Authorize(Policy = Permissions.UserCreate)]  
 public ActionResult Create()    {        return View();}[Authorize(Policy = Permissions.UserCreate)][HttpPost][ValidateAntiForgeryToken]  
 public IActionResult Create([Bind("Title")] User user)    {} }

当然,我们也可以像基于资源的授权那样,在应用代码中调用IAuthorizationService完成授权,这样做的好处是无需定义策略,但是,显然一个一个来定义策略太过于繁琐。

还有一种更好方式,就是使用MVC过滤器来完成对IAuthorizationService的调用,下面就来演示一下。

自定义授权过滤器

我们可以参考上一章中介绍的《AuthorizeFilter》来自定义一个权限过滤器:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class PermissionFilter : Attribute, IAsyncAuthorizationFilter{    public PermissionFilter(string name)    {Name = name;}    

public string Name { get; set; }  

public async Task OnAuthorizationAsync(AuthorizationFilterContext context)    {        var authorizationService = context.HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();        var authorizationResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, null, new PermissionAuthorizationRequirement(Name));      
 if (!authorizationResult.Succeeded){context.Result = new ForbidResult();}} }

上面的实现非常简单,我们接受一个name参数,代表权限的名称,然后将权限名称转化为PermissionAuthorizationRequirement,最后直接调用 authorizationService 来完成授权。

接下来,我们就可以直接在控制器中使用PermissionFilter过滤器来完成基于权限的授权了:

[Authorize]
public class UserController : Controller{[PermissionFilter(Permissions.UserRead)]  
 public ActionResult Index()    {      
  return View(_userStore.GetAll());}[PermissionFilter(Permissions.UserCreate)]  
 public ActionResult Create()    {}[PermissionFilter(Permissions.UserCreate)][HttpPost][ValidateAntiForgeryToken]  
 public IActionResult Create([Bind("Title")] User user)    {}[PermissionFilter(Permissions.UserUpdate)]  
 public IActionResult Edit(int? id)    {}[PermissionFilter(Permissions.UserUpdate)][HttpPost][ValidateAntiForgeryToken]  
 public IActionResult Edit(int id, [Bind("Id,Title")] User user)    {} } 

在视图中使用授权

通常,在前端页面当中,我们也需要根据用户的权限来判断是否显示“添加”,“删除”等按钮,而不是让用户点击“添加”,再提示用户没有权限,这在 ASP.NET Core 中实现起来也非常简单。

我们可以直接在Razor视图中注入IAuthorizationService来检查用户权限:

@inject IAuthorizationService AuthorizationService@if ((await AuthorizationService.AuthorizeAsync(User, AuthorizationSample.Authorization.Permissions.UserCreate)).Succeeded)
{    <p><a asp-action="Create">创建</a></p>}

不过,上面的代码是通过策略名称来授权的,如果我们使用了上面创建的授权过滤器,而没有定义授权策略的话,需要使用如下方式来实现:

@inject IAuthorizationService AuthorizationService@if ((await AuthorizationService.AuthorizeAsync(User, new PermissionAuthorizationRequirement(AuthorizationSample.Authorization.Permissions.UserCreate))).Succeeded)
{    <p><a asp-action="Create">创建</a></p>}

我们也可以定义一个AuthorizationService的扩展方法,实现通过权限名称进行授权,这里就不再多说。

我们不能因为隐藏了操作按钮,就不在后端进行授权验证了,就像JS的验证一样,前端的验证就为了提升用户的体验,后端的验证在任何时候都是必不可少的。

总结

在大多数场景下,我们只需要使用授权策略就可以应对,而在授权策略不能满足我们的需求时,由于 ASP.NET Core 提供了一个统一的 IAuthorizationService 授权接口,这就使我们扩展起来也非常方便。ASP.NET Core 的授权部分到这来也就介绍完了,总的来说,要比ASP.NET 4.x的时候,简单,灵活很多,可见 ASP.NET Core 不仅仅是为了跨平台,而是为了适应现代应用程序的开发方式而做出的全新的设计,我们也应该用全新的思维去学习.NET Core,踏上时代的浪潮。

相关文章:

  • ASP.NET Core 认证与授权[4]:JwtBearer认证

  • ASP.NET Core 认证与授权[2]:Cookie认证

  • ASP.NET Core 认证与授权[3]:OAuth & OpenID Connect认证

  • Asp.Net Core 2.0 多角色权限认证

  • asp.net core 2.0 web api基于JWT自定义策略授权

  • ASP.NET Core 认证与授权[5]:初识授权

  • ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?


原文:http://www.cnblogs.com/RainingNight/p/dynamic-authorization-in-asp-net-core.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/322778.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

H5的Websocket基本使用

前端代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head> &…

springboot手动提交kafka offset

转载自 springboot手动提交kafka offset enable.auto.commit参数设置成了false 但是测试发现enable.auto.commit参数设置成了false&#xff0c;kafka的offset依然提交了&#xff08;也没有进行人工提交offset&#xff09;。 查看源码 如果我们enable.auto.commit设置为false…

可观测性与原生云监控

在近日发表的一篇文章中&#xff0c;Cindy Sridharan概括介绍了可观测性及其与原生云应用程序监控的关系。可观测性是一种理念&#xff0c;包括监控、日志聚合、指标和分布式跟踪&#xff0c;可以实时更深入地观察系统。 Sridharan的文章基于她就同一个主题所做的Velocity演讲。…

使用Microsoft.AspNetCore.TestHost进行完整的功能测试

简介 Microsoft.AspNetCore.TestHost是可以用于Asp.net Core 的功能测试工具。很多时候我们一个接口写好了&#xff0c;单元测试什么的也都ok了&#xff0c;需要完整调试一下&#xff0c;检查下单元测试未覆盖到的代码是否有bug。步骤为如下&#xff1a;程序打个断点->F5运行…

MockJs案例

有时候前端写好模板后&#xff0c;后端还完工&#xff0c;那么总不能一直让项目停滞吧&#xff0c;这里就用Mockjs来模拟后端接口的数据&#xff0c;让我们先人一步完成项目。 首先创建一个html&#xff0c;导入axios和mockjs 再用mock去拦截请求&#xff0c;如果后端接口写好了…

Entity Framework Core 使用HiLo生成主键

HiLo是在NHibernate中生成主键的一种方式&#xff0c;不过现在我们可以在Entity Framework Core中使用。所以在这篇内容中&#xff0c;我将向您在介绍如何在Entity Framework Core中使用HiLo生成主键。 什么是Hilo&#xff1f; HiLo是High Low的简写&#xff0c;翻译成中文叫高…

Echarts报错:Component series.lines not exists. Load it first.

前几天用的echarts标签是bootcdn的 <script src"https://cdn.bootcdn.net/ajax/libs/echarts/4.7.0/echarts-en.common.js"></script>用着官方给的案例还可以&#xff0c;但是一用gallery社区里面的例子就报错 后来经过不断调试终于知道是需要换个cdn&…

认识微软Visual Studio Tools for AI

微软已经发布了其 Visual Studio Tools for AI 的测试版本&#xff0c;这是微软 Visual Studio 2017 IDE 的扩展&#xff0c;可以让开发人员和数据科学家将深度学习模型嵌入到应用程序中。Visual Studio Tools for AI 工具同时支持 Microsoft 的 Cognitive Toolkit 和 Google 的…

mybatis源码阅读(一):SqlSession和SqlSessionFactory

转载自 mybatis源码阅读(一)&#xff1a;SqlSession和SqlSessionFactory 一、接口定义 听名字就知道这里使用了工厂方法模式&#xff0c;SqlSessionFactory负责创建SqlSession对象。其中开发人员最常用的就是DefaultSqlSession &#xff08;1&#xff09;SqlSession接口定义…

开源纯C#工控网关+组态软件(六)图元组件

一、 图元概述 图元是构成人机界面的基本单元。如一个个的电机、设备、数据显示、仪表盘&#xff0c;都是图元。构建人机界面的过程就是铺排、挪移、定位图元的过程。 图元设计是绘图和编码的结合。因为图元不仅有显示和动画&#xff0c;还有背后操纵动画的控制逻辑。 一个好…

git合并分支的策略(赞)

假设当前有两个分支 master和test&#xff0c;两个分支一模一样&#xff0c;都有这三个文件 现在test添加一个test4.txt&#xff0c;然后提交到本地&#xff08;git add . git commit&#xff09; 切换到master分支上&#xff0c;git checkout master git merge test 这样mase…

改造独立部署(SCD)模式下.NET Core应用程序 dotnet的exe文件启动过程

设置一个小目标 改造前 改造后 独立部署SCD模式&#xff0c;是指在使用dotnet publish 命令时带上-r 参数运行时标识符&#xff08;RID&#xff09;。 目标提出原因&#xff1a;SCD模式下文件太乱了&#xff0c;很多文件在开发时大多又涉及不到&#xff0c;发布后如果能把文件…

mybatis源码阅读(四):mapper(dao)实例化

转载自 mybatis源码阅读(四)&#xff1a;mapper(dao)实例化 在开始分析之前&#xff0c;先来了解一下这个模块中的核心组件之间的关系&#xff0c;如图&#xff1a; 1.MapperRegistry&MapperProxyFactory MapperRegistry是Mapper接口及其对应的代理对象工程的注册中心&…

自定义路由匹配和生成

前言 前两篇文章主要总结了CMS系统两个技术点在ASP.NET Core中的应用&#xff1a; 《ASP.NET Core 中的SEO优化&#xff08;1&#xff09;&#xff1a;中间件实现服务端静态化缓存》 《ASP.NET Core 中的SEO优化&#xff08;2&#xff09;&#xff1a;中间件中渲染Razor视图》…

如何封装并发布一个属于自己的ui组件库

以前就一直有个想法自己能不能封装一个类似于elementui一样的组件库&#xff0c;然后发布到npm上去&#xff0c;毕竟前端说白了&#xff0c;将组件v上去&#xff0c;然后进行数据交互。借助这次端午&#xff0c;终于有机会&#xff0c;尝试自己去封装发布组件库了 我这里了只做…

听云支持.NET Core的应用性能监控

随着微软于2017年8月正式发布.NET Core 2.0&#xff0c; .NET Core 社区开始活跃&#xff0c;众多.NET开发者开始向跨平台转变。 听云于2017年11月推出了.NET Core应用监控工具&#xff0c;和听云其他语言的监控工具一样&#xff0c;.NET Core应用监控工具具有以下特征&#xf…

mybatis源码阅读(五) ---执行器Executor

转载自 mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_RESULT_HANDLER null;// 执行update&#xff0c;delete&#xff0c;insert三种类型的sql语句int update(MappedStatement ms, Object parameter…

[52ABP实战系列] .NET CORE实战入门第三章更新了

早安 各位道友好&#xff0c;.NET CORE入门视频的第三章也算录制完毕了。欢迎大家上传课网进行学习。 更新速度 大家也知道最近的社会新闻比较多。频繁发生404、关键字打不出来&#xff0c;我个人也在关注这些事件。导致精力分散&#xff0c;没有做到稳定更新&#xff0c;现在呢…

如何安装nuxt

因为vue是单页面应用&#xff0c;所以不被Seo&#xff0c;如百度和Google抓取到&#xff0c;在Vue中如果想要爬虫爬到就必须使用nuxt 那么如何安装使用呢&#xff1f; yarn create nuxt-app <project-name> cd <project-name> yarn build yarn start必须先build&a…

mybatis源码阅读(六) ---StatementHandler了解一下

转载自 mybatis源码阅读(六) ---StatementHandler了解一下 StatementHandler类结构图与接口设计 BaseStatementHandler&#xff1a;一个抽象类&#xff0c;只是实现了一些不涉及具体操作的方法 RoutingStatementHandler&#xff1a;类似路由器&#xff0c;根据配置文件来路由…