环球ug官网:.Net Core实战之基于角色的接见控制的设计,.Net微服务实战之手艺架构分层篇,.Net微服务实战之手艺选型篇

2020-06-11 45 views 0

扫一扫用手机浏览

前言

  上个月,我写了两篇微服务的文章:《.Net微服务实战之手艺架构分层篇》与《.Net微服务实战之手艺选型篇》,微服务系列原有三篇,当我憋第三篇的内容时刻一直没有灵感,因此先计划放一放。

  本篇文章与源码原本计划着实去年的时刻完成并公布的,然而我一直忙于公司项目的微服务的实行,以是该篇文章一拖再拖。现在我花了点时间整理了下代码,并以此篇文章形貌整个实现思绪,并开放了源码给予需要的人一些参考。

  源码:https://github.com/SkyChenSky/Sikiro.RBAC

RBAC

  Role-Based Access Contro翻译成中文就是基于角色的接见控制,文章以下我都用他的简称RBAC来形貌。

  现信息系统的权限控制大多数接纳RBAC的头脑举行实现,其本质头脑是对系统种种的操作权限不是直接授予详细的某个用户,而是在用户聚集与权限聚集之间确立一个角色,作为间接关联。每一种角色对应一组响应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。

  通过以上的形貌,我们可以分析出以下信息:

  •   用户与权限是通过角色间接关联的
  •   角色的本质就是权限组(权限聚集)

  这样做的利益在于,不必在每次建立用户时都举行分配权限的操作,只要分配用户响应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限治理,削减系统的开销。

  

功效分析

权限分类

从权限的作用可以分为三种,功效权限、接见权限、数据权限

  • 功效权限
    • 功效权限指系统用户允许在页面举行按钮操作的权限。若是有权限则功效按钮展示,否则隐藏。
  • 接见权限
    • 接见权限指系统用户通过点击按钮后举行地址的请求接见的权限(地址跳转与接口请求),若是无权限接见,则由页面提醒无权限接见。
  • 数据权限
    • 数据权限指用户可接见系统的数据权限,差别的用户可以接见差别的数据粒度。

数据权限的实现小大由之,大可大到对条件举行动态设置,小可小到只针对某个维度举行硬编码。不纳入这次的讨论局限。

用例图

非功效性需求

  时效性,直接影响到安全性,既然是权限控制,那么理应一修改权限后就马上生效。曾经有偕行问过我,是不是每一个请求都得去查一次数据库是否知足权限,若是是,数据库压力岂不是很大?

  安全性,每一个页面跳转,每一个读写请求都的举行一次权限验证,不知足的权限的功效按钮就不需要渲染,制止样式display:none的情形。

  开发效率,权限控制理应是框架层面的,因此尽可能作为非营业的侵入性,让开发人员保持原有的数据善增改查与页面渲染。

手艺选型

LayUI

  学习门槛极低,开箱即用。其外在极简,却又不失丰满的内在,体积轻盈,组件丰盈,从焦点代码到 API 的每一处细节都经由经心雕琢,异常适合界面的快速开发,它更多是为服务端程序员量身定做,无需涉足种种前端工具的庞大设置,只需面临浏览器自己,让一切你所需要的元素与交互,从这里信手拈来。作为国人的开源项目,完整的接口文档与Demo示例让入门者异常友好的上手,开箱即用的Api让学习成本尽可能的低,其易用性成为快速开发框架的基础。

MongoDB

  主要两大优势,无模式与横向扩展。对于权限模块来说,无需SQL来写庞大查询和报表,也不需要使用到多表的强事务,上面提到的时效性的数据库压力问题也可以通过分片解决。无模式使得开发人员无需预界说存储结构,连系MongoDB官方提供的驱动可以做到快速的开发。

数据库设计

 E-R图

 

  一个治理员可以拥有多个角色,因此治理员与角色是一对多的关联;角色作为权限组的存在,又可以选择多个功效权限值与菜单,以是角色与菜单、功效权限值也是一对多的关系。

类图

Sunbet,进入申博Sunbet官网  第1张

Deparment与Position属于非焦点,可以根据自己的现实营业举行扩展。

功效权限值初始化

  随着营业生长,需求功效是千奇百怪的,基本无法抽象出来,那么功效按钮就要随着营业举行界说。在我的项目里使用了枚举值举行界说每个功效权限,通过自界说的PermissionAttribute与响应的action举行绑定,在系统启动时,通过反射把功效权限的枚举值与响应的controller、action映射到MenuAction表,枚举值对应code字段,controller与action拼接后对应url字段。

  已初始化到数据库的权限值可以到菜单页把相对应的菜单与权限通过用户界面关联起来。

权限值绑定action

1         [HttpPost]
2         [Permission(PermCode.Administrator_Edit)]
3         public IActionResult Edit(EditModel edit)
4         {
5             //do something
6 
7             return Json(result);
8         }

初始化权限值

 1     /// <summary>
 2     /// 功效权限
 3     /// </summary>
 4     public static class PermissionUtil
 5     {
 6         public static readonly Dictionary<string, IEnumerable<int>> PermissionUrls = new Dictionary<string, IEnumerable<int>>();
 7         private static MongoRepository _mongoRepository;
 8 
 9         /// <summary>
10         /// 判断权限值是否被重复使用
11         /// </summary>
12         public static void ValidPermissions()
13         {
14             var codes = Enum.GetValues(typeof(PermCode)).Cast<int>();
15             var dic = new Dictionary<int, int>();
16             foreach (var code in codes)
17             {
18                 if (!dic.ContainsKey(code))
19                     dic.Add(code, 1);
20                 else
21                     throw new Exception($"权限值 {code} 被重复使用,请检查 PermCode 的界说");
22             }
23         }
24 
25         /// <summary>
26         /// 初始化添加预界说权限值
27         /// </summary>
28         /// <param name="app"></param>
29         public static void InitPermission(IApplicationBuilder app)
30         {
31             //验证权限值是否重复
32             ValidPermissions();
33 
34             //反射被符号的Controller和Action
35             _mongoRepository = (MongoRepository)app.ApplicationServices.GetService(typeof(MongoRepository));
36 
37             var permList = new List<MenuAction>();
38             var actions = typeof(PermissionUtil).Assembly.GetTypes()
39                 .Where(t => typeof(Controller).IsAssignableFrom(t) && !t.IsAbstract)
40                 .SelectMany(t => t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly));
41 
42             //遍历聚集整理信息
43             foreach (var action in actions)
44             {
45                 var permissionAttribute =
46                     action.GetCustomAttributes(typeof(PermissionAttribute), false).ToList();
47                 if (!permissionAttribute.Any())
48                     continue;
49 
50                 var codes = permissionAttribute.Select(a => ((PermissionAttribute)a).Code).ToArray();
51                 var controllerName = action?.ReflectedType?.Name.Replace("Controller", "").ToLower();
52                 var actionName = action.Name.ToLower();
53 
54                 foreach (var item in codes)
55                 {
56                     if (permList.Exists(c => c.Code == item))
57                     {
58                         var menuAction = permList.FirstOrDefault(a => a.Code == item);
59                         menuAction?.Url.Add($"{controllerName}/{actionName}".ToLower());
60                     }
61                     else
62                     {
63                         var perm = new MenuAction
64                         {
65                             Id = item.ToString().EncodeMd5String().ToObjectId(),
66                             CreateDateTime = DateTime.Now,
67                             Url = new List<string> { $"{controllerName}/{actionName}".ToLower() },
68                             Code = item,
69                             Name = ((PermCode)item).GetDisplayName() ?? ((PermCode)item).ToString()
70                         };
71                         permList.Add(perm);
72                     }
73                 }
74                 PermissionUrls.TryAdd($"{controllerName}/{actionName}".ToLower(), codes);
75             }
76 
77             //营业功效持久化
78             _mongoRepository.Delete<MenuAction>(a => true);
79             _mongoRepository.BatchAdd(permList);
80         }
81 
82         /// <summary>
83         /// 获取当前路径
84         /// </summary>
85         /// <param name="filterContext"></param>
86         /// <returns></returns>
87         public static string CurrentUrl(HttpContext filterContext)
88         {
89             var url = filterContext.Request.Path.ToString().ToLower().Trim('/');
90             return url;
91         }
92     }

关联菜单与功效权限

Sunbet,进入申博Sunbet官网  第2张

接见权限

  当所有权限关系关联上后,用户接见系统时,需要对其所有操作举行阻挡与实时的权限判断,我们注册一个全局的GlobalAuthorizeAttribute,其主要阻挡所有已经标识PermissionAttribute的action,查询该用户所关联所有角色的权限是否知足允许通过。

  我的实现有个细节,给判断用户IsSuper==true,也就是超级治理员,若是是超级治理员则绕过所有判断,可能有人会问为什么不在角色添加一个名叫超级治理员举行判断,由于名称是不可控的,在代码逻辑里并不知道用户起的所谓的超级治理员,就是我们需要绕过验证的超级治理员,如果他叫无敌治理员呢?

 1  /// <summary>
 2     /// 全局的接见权限控制
 3     /// </summary>
 4     public class GlobalAuthorizeAttribute : System.Attribute, IAuthorizationFilter
 5     {
 6         #region 初始化
 7         private string _currentUrl;
 8         private string _unauthorizedMessage;
 9         private readonly List<string> _noCheckPage = new List<string> { "home/index", "home/indexpage", "/" };
10 
11         private readonly AdministratorService _administratorService;
12         private readonly MenuService _menuService;
13 
14         public GlobalAuthorizeAttribute(AdministratorService administratorService, MenuService menuService)
15         {
16             _administratorService = administratorService;
17             _menuService = menuService;
18         } 
19         #endregion
20 
21         public void OnAuthorization(AuthorizationFilterContext context)
22         {
23             context.ThrowIfNull();
24 
25             _currentUrl = PermissionUtil.CurrentUrl(context.HttpContext);
26 
27             //不需要验证登录的直接跳过
28             if (context.Filters.Count(a => a is AllowAnonymousFilter) > 0)
29                 return;
30 
31             var user = GetCurrentUser(context);
32             if (user == null)
33             {
34                 if (_noCheckPage.Contains(_currentUrl))
35                     return;
36 
37                 _unauthorizedMessage = "登录失效";
38 
39                 if (context.HttpContext.Request.IsAjax())
40                     NoUserResult(context);
41                 else
42                     LogoutResult(context);
43                 return;
44             }
45 
46             //超级治理员跳过
47             if (user.IsSuper)
48                 return;
49 
50             //账号状态判断
51             var administrator = _administratorService.GetById(user.UserId);
52             if (administrator != null && administrator.Status != EAdministratorStatus.Normal)
53             {
54                 if (_noCheckPage.Contains(_currentUrl))
55                     return;
56 
57                 _unauthorizedMessage = "亲~您的账号已被停用,若有需要请您联系系统治理员";
58 
59                 if (context.HttpContext.Request.IsAjax())
60                     AjaxResult(context);
61                 else
62                     AuthResult(context, 403, GoErrorPage(true));
63 
64                 return;
65             }
66 
67             if (_noCheckPage.Contains(_currentUrl))
68                 return;
69 
70             var userUrl = _administratorService.GetUserCanPassUrl(user.UserId);
71 
72             // 判断菜单接见权限与菜单接见权限
73             if (IsMenuPass(userUrl) && IsActionPass(userUrl))
74                 return;
75 
76             if (context.HttpContext.Request.IsAjax())
77                 AuthResult(context, 200, GetJsonResult());
78             else
79                 AuthResult(context, 403, GoErrorPage());
80         }
81     }

功效权限

  在权限验证通事后,返回view之前,照样行使了Filter举行一个实时的权限查询,主要把该用户所拥有功效权限值查询出来通过ViewData["PermCodes"]传到页面,然后通过razor举行按钮的渲染判断。

  然而我在项目中封装了大部分常用的LayUI控件,主要行使.Net Core的TagHelper举行了封装,TagHelper内部与ViewData["PermCodes"]举行判断是否输出HTML。

全局功效权限值查询

 1 /// <summary>
 2     /// 全局用户权限值查询
 3     /// </summary>
 4     public class GobalPermCodeAttribute : IActionFilter
 5     {
 6         private readonly AdministratorService _administratorService;
 7 
 8         public GobalPermCodeAttribute(AdministratorService administratorService)
 9         {
10             _administratorService = administratorService;
11         }
12 
13         private static AdministratorData GetCurrentUser(HttpContext context)
14         {
15             return context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.UserData)?.Value.FromJson<AdministratorData>();
16         }
17 
18 
19         public void OnActionExecuting(ActionExecutingContext context)
20         {
21             ((Controller)context.Controller).ViewData["PermCodes"] = new List<int>();
22 
23             if (context.HttpContext.Request.IsAjax())
24                 return;
25 
26             var user = GetCurrentUser(context.HttpContext);
27             if (user == null)
28                 return;
29 
30             if (user.IsSuper)
31                 return;
32 
33             ((Controller)context.Controller).ViewData["PermCodes"] = _administratorService.GetActionCode(user.UserId).ToList();
34         }
35 
36         public void OnActionExecuted(ActionExecutedContext context)
37         {
38         }
39     }

LayUI Buttom的TagHelper封装

 1   [HtmlTargetElement("LayuiButton")]
 2     public class LayuiButtonTag : TagHelper
 3     {
 4         #region 初始化
 5         private const string PermCodeAttributeName = "PermCode";
 6         private const string ClasstAttributeName = "class";
 7         private const string LayEventAttributeName = "lay-event";
 8         private const string LaySubmitAttributeName = "LaySubmit";
 9         private const string LayIdAttributeName = "id";
10         private const string StyleAttributeName = "style";
11 
12         [HtmlAttributeName(StyleAttributeName)]
13         public string Style { get; set; }
14 
15         [HtmlAttributeName(LayIdAttributeName)]
16         public string Id { get; set; }
17 
18         [HtmlAttributeName(LaySubmitAttributeName)]
19         public string LaySubmit { get; set; }
20 
21         [HtmlAttributeName(LayEventAttributeName)]
22         public string LayEvent { get; set; }
23 
24         [HtmlAttributeName(ClasstAttributeName)]
25         public string Class { get; set; }
26 
27         [HtmlAttributeName(PermCodeAttributeName)]
28         public int PermCode { get; set; }
29 
30         [HtmlAttributeNotBound]
31         [ViewContext]
32         public ViewContext ViewContext { get; set; }
33 
34         #endregion
35         public override async void Process(TagHelperContext context, TagHelperOutput output)
36         {
37             context.ThrowIfNull();
38             output.ThrowIfNull();
39 
40             var administrator = ViewContext.HttpContext.GetCurrentUser();
41             if (administrator == null)
42                 return;
43 
44             var childContent = await output.GetChildContentAsync();
45 
46             if (((List<int>)ViewContext.ViewData["PermCodes"]).Contains(PermCode) || administrator.IsSuper)
47             {
48                 foreach (var item in context.AllAttributes)
49                 {
50                     output.Attributes.Add(item.Name, item.Value);
51                 }
52 
53                 output.TagName = "a";
54                 output.TagMode = TagMode.StartTagAndEndTag;
55                 output.Content.SetHtmlContent(childContent.GetContent());
56             }
57             else
58             {
59                 output.TagName = "";
60                 output.TagMode = TagMode.StartTagAndEndTag;
61                 output.Content.SetHtmlContent("");
62             }
63         }
64     }

 

视图代码

Sunbet,进入申博Sunbet官网  第3张

末端

  以上就是我本篇分享的内容,项目是以单体应用提供的,方案思绪也适用于前后端星散。最后附上几个系统效果图

 

 

Sunbet,进入申博Sunbet官网  第4张

 

Sunbet,进入申博Sunbet官网  第5张

Sunbet,进入申博Sunbet官网  第6张

,

欧博亚洲客户端

欢迎进入欧博亚洲客户端(Allbet Game):www.aLLbetgame.us,欧博官网是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

Sunbet网站内容转载自互联网,如有侵权,联系Sunbet 删除。

本文链接地址:http://www.shfkgcjxyxgs.com/post/1518.html

相关文章

发表评论