前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Core + Vue 后台管理基础框架3——后端授权

Core + Vue 后台管理基础框架3——后端授权

作者头像
guokun
发布2020-09-03 15:51:42
5340
发布2020-09-03 15:51:42
举报
文章被收录于专栏:销声匿迹销声匿迹

1、前言

  但凡业务系统,授权是绕不开的一环。见过太多只在前端做菜单及按钮显隐控制,但后端裸奔的,觉着前端看不到,系统就安全,掩耳盗铃也好,自欺欺人也罢,这里不做评论。在.NET CORE中,也见过不少用操作过滤器来实现业务用例权限控制的,至少算是对后端做了权限控制。

  但我们知道,操作过滤器,已经算是过滤器管道中最靠后的,基本上紧挨着我们控制器方法执行那里了,本身,操作过滤器也不是做权限控制的地方,虽然本身它能达到权限控制效果。为什么这么说,试想下,在过滤器管道之前,还有中间件处理管道,即便是过滤器管道执行环节,操作过滤器也是最靠后的,它往前还有授权过滤器,资源过滤器等,假如我在资源过滤器中缓存了请求结果,那权限控制基本上就废了。

  说这么多,只想表达一点,合适的地方,合适的东西,干合适的事儿。在.NET CORE中,官方推荐用策略去实现授权。策略授权,是在授权中间件环节执行,当然能解决上述执行流程先后顺序的问题。但如果要直接应用于我们业务系统中的权限控制,恐怕远远不够,因为你不可能为每个api用例创建一个角色或策略,更主要的,权限控制还要动态授予或回收的,不做扩展直接照搬,你是很难搞的。接下来,我们就来看看,如何基于core的授权机制,去实现我们传统的用户,角色,权限,及权限的动态授予与回收控制。

2、实现

  我们先看看,菜单表概览:

  查询中IsMenu代表是侧边栏菜单还是功能按钮,这里我把按钮级别的给筛选出来了,每个按钮菜单都代表一个业务用例,也对应我们一个控制器方法。 Code是唯一的,待会儿权限控制标识,会采用这个字段。当然你用主键ID也可以,但比较难记。实际运维中,会把这些菜单按照业务分配给指定角色,再把指定角色分配给系统用户。

  接下来,定义一个Requirement,以权限码作为主校验对象:

代码语言:javascript
复制
public class PermissionRequirement : IAuthorizationRequirement
    {
        public PermissionRequirement(params string[] codes)
        {
            this.Codes = codes;
        }

        public string[] Codes { get; private set; }
    }

  有Requirement,自然有RequirementHandler:

代码语言:javascript
复制
 1 public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
 2     {
 3         private readonly IMenuService _menuService;
 4 
 5         public PermissionHandler(IMenuService menuService)
 6         {
 7             _menuService = menuService;
 8         }
 9 
10         protected override async Task HandleRequirementAsync(
11             AuthorizationHandlerContext context,
12             PermissionRequirement requirement)
13         {
14             var roles = context.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role).Value;
15             if (!string.IsNullOrWhiteSpace(roles))
16             {
17                 var roleIds = roles.Split(',', StringSplitOptions.RemoveEmptyEntries)
18                     .Select(x => long.Parse(x));
19                 if (roleIds.Contains(1))
20                 {
21                     context.Succeed(requirement);
22                     return;
23                 }
24 
25                 var menus = await _menuService.GetMenusByRoleIds(roleIds.ToArray());
26                 if (menus.Any())
27                 {
28                     var codes = menus.Select(x => x.Code.ToUpper());
29                     if (requirement.Codes.Any(x => codes.Contains(x.ToUpper())))
30                     {
31                         context.Succeed(requirement);
32                     }
33                 }
34             }
35         }
36     }

  逻辑比较常规,根据当前用户角色,获取其所有菜单权限,然后与Requirement中声明要求的菜单权限做对比,如果含有,则放行。到这儿,大家应该都能看懂,典型的.NET CORE权限控制组件。

  接下来,定义一个授权过滤器特性:

代码语言:javascript
复制
 1 public class PermissionFilter : Attribute, IAsyncAuthorizationFilter
 2     {
 3         private readonly IAuthorizationService _authorizationService;
 4         private readonly PermissionRequirement _requirement;
 5 
 6         public PermissionFilter(IAuthorizationService authorizationService, PermissionRequirement requirement)
 7         {
 8             _authorizationService = authorizationService;
 9             _requirement = requirement;
10         }
11 
12         public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
13         {
14             var result = await _authorizationService.AuthorizeAsync(context.HttpContext.User, null, _requirement);
15 
16             if (!result.Succeeded)
17             {
18                 context.Result = new JsonResult("您没有此操作权限")
19                 {
20                     StatusCode = (int)HttpStatusCode.Forbidden
21                 };
22             }
23         }
24     }

  从这儿开始,我估计有人就要看不懂了,下边解释下。首先,PermissionFilter是个授权过滤器,它实现了IAsyncAuthorizationFilter,所以会作为过滤器管道第一道去执行。构造函数中有2个参数,IAuthorizationService直接注入,PermissionRequirement则通过特性标记外部设置。在OnAuthorizationAsync重写方法中,调用IAuthorizationService.AuthorizeAsync方法去做具体授权校验工作,经此桥接,授权流程就转到我们最开始定义的PermissionHandler去了。本身core源码中,IAuthorizationService是在授权中间件中使用到的,这里我借用了。注意,一旦过滤器注入了服务,那此过滤器便不再能够直接以打标记的形式贴在控制器或方法上了,那种形式必须所有参数都直接指定。core中给出的方案,是TypeFilter,涉及到服务注入的时候。

  那接下来,就是另一个重要对象了:

代码语言:javascript
复制
/// <summary>
    /// 权限特性
    /// </summary>
    public class PermissionAttribute : TypeFilterAttribute
    {
        public PermissionAttribute(params string[] codes)
            : base(typeof(PermissionFilter))
        {
            Arguments = new[] { new PermissionRequirement(codes) };
        }
    }

  至此,各组件定义完毕,那我们使用就类似下边这样:

3、效果演示

  guokun用户角色:

网站管理员角色对应权限:

  可以看到,是没有勾选删除部门的,那我们用这个账户来删除下部门:

  状态码403,并提示无权操作,删除动作已经被拦截了。那我们把删除权限赋予网站管理员:

  接下来再来删除:

  可以看到,已经删除部门成功。

3、总结

  以上便是本项目权限控制的实现。认证&授权这块儿,如果要做好,还是得把core的整套机制弄清楚,最好能把源码过一遍,不然根本搞不清楚需要怎么扩展,每个扩展点在什么时机触发及生效。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-03-14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
消息队列 TDMQ
消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档