青岛网站建设 推荐青岛博采网络,设计网站页面步骤,微信小程序卖货怎么做,做网站源码前言上个月#xff0c;我写了两篇微服务的文章#xff1a;《.Net微服务实战之技术架构分层篇》与《.Net微服务实战之技术选型篇》#xff0c;微服务系列原有三篇#xff0c;当我憋第三篇的内容时候一直没有灵感#xff0c;因此先打算放一放。本篇文章与源码原本打算实在去… 前言 上个月我写了两篇微服务的文章《.Net微服务实战之技术架构分层篇》与《.Net微服务实战之技术选型篇》微服务系列原有三篇当我憋第三篇的内容时候一直没有灵感因此先打算放一放。 本篇文章与源码原本打算实在去年的时候完成并发布的然而我一直忙于公司项目的微服务的实施所以该篇文章一拖再拖。如今我花了点时间整理了下代码并以此篇文章描述整个实现思路并开放了源码给予需要的人一些参考。 源码https://github.com/SkyChenSky/Sikiro.RBACRBAC Role-Based Access Contro翻译成中文就是基于角色的访问控制文章以下我都用他的简称RBAC来描述。 现信息系统的权限控制大多数采取RBAC的思想进行实现其本质思想是对系统各种的操作权限不是直接授予具体的某个用户而是在用户集合与权限集合之间建立一个角色作为间接关联。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后该用户就拥有此角色的所有操作权限。 通过以上的描述我们可以分析出以下信息 用户与权限是通过角色间接关联的 角色的本质就是权限组权限集合 这样做的好处在于不必在每次创建用户时都进行分配权限的操作只要分配用户相应的角色即可而且角色的权限变更比用户的权限变更要少得多这样将简化用户的权限管理减少系统的开销。 功能分析权限分类从权限的作用可以分为三种功能权限、访问权限、数据权限功能权限功能权限指系统用户允许在页面进行按钮操作的权限。如果有权限则功能按钮展示否则隐藏。访问权限访问权限指系统用户通过点击按钮后进行地址的请求访问的权限地址跳转与接口请求如果无权限访问则由页面提示无权限访问。数据权限数据权限指用户可访问系统的数据权限不同的用户可以访问不同的数据粒度。数据权限的实现可大可小大可大到对条件进行动态配置小可小到只针对某个维度进行硬编码。不纳入这次的讨论范围。用例图非功能性需求 时效性直接影响到安全性既然是权限控制那么理应一修改权限后就立刻生效。曾经有同行问过我是不是每一个请求都得去查一次数据库是否满足权限如果是数据库压力岂不是很大 安全性每一个页面跳转每一个读写请求都的进行一次权限验证不满足的权限的功能按钮就不需要渲染避免样式display:none的情况。 开发效率权限控制理应是框架层面的因此尽可能作为非业务的侵入性让开发人员保持原有的数据善增改查与页面渲染。技术选型LayUI 学习门槛极低开箱即用。其外在极简却又不失饱满的内在体积轻盈组件丰盈从核心代码到 API 的每一处细节都经过精心雕琢非常适合界面的快速开发,它更多是为服务端程序员量身定做无需涉足各种前端工具的复杂配置只需面对浏览器本身让一切你所需要的元素与交互从这里信手拈来。作为国人的开源项目完整的接口文档与Demo示例让入门者非常友好的上手开箱即用的Api让学习成本尽可能的低其易用性成为快速开发框架的基础。MongoDB 主要两大优势无模式与横向扩展。对于权限模块来说无需SQL来写复杂查询和报表也不需要使用到多表的强事务上面提到的时效性的数据库压力问题也可以通过分片解决。无模式使得开发人员无需预定义存储结构结合MongoDB官方提供的驱动可以做到快速的开发。数据库设计 E-R图 一个管理员可以拥有多个角色因此管理员与角色是一对多的关联角色作为权限组的存在又可以选择多个功能权限值与菜单所以角色与菜单、功能权限值也是一对多的关系。类图Deparment与Position属于非核心可以按照自己的实际业务进行扩展。功能权限值初始化 随着业务发展需求功能是千奇百怪的根本无法抽象出来那么功能按钮就要随着业务进行定义。在我的项目里使用了枚举值进行定义每个功能权限通过自定义的PermissionAttribute与响应的action进行绑定在系统启动时通过反射把功能权限的枚举值与相应的controller、action映射到MenuAction表枚举值对应code字段controller与action拼接后对应url字段。 已初始化到数据库的权限值可以到菜单页把相对应的菜单与权限通过用户界面关联起来。权限值绑定action1 [HttpPost]
2 [Permission(PermCode.Administrator_Edit)]
3 public IActionResult Edit(EditModel edit)
4 {
5 //do something
6
7 return Json(result);
8 }初始化权限值 1 /// summary2 /// 功能权限3 /// /summary4 public static class PermissionUtil5 {6 public static readonly Dictionarystring, IEnumerableint PermissionUrls new Dictionarystring, IEnumerableint();7 private static MongoRepository _mongoRepository;8 9 /// summary
10 /// 判断权限值是否被重复使用
11 /// /summary
12 public static void ValidPermissions()
13 {
14 var codes Enum.GetValues(typeof(PermCode)).Castint();
15 var dic new Dictionaryint, 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 nameapp/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 ListMenuAction();
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 Liststring { ${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.DeleteMenuAction(a true);
79 _mongoRepository.BatchAdd(permList);
80 }
81
82 /// summary
83 /// 获取当前路径
84 /// /summary
85 /// param namefilterContext/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 }关联菜单与功能权限访问权限 当所有权限关系关联上后用户访问系统时需要对其所有操作进行拦截与实时的权限判断我们注册一个全局的GlobalAuthorizeAttribute其主要拦截所有已经标识PermissionAttribute的action查询该用户所关联所有角色的权限是否满足允许通过。 我的实现有个细节给判断用户IsSupertrue也就是超级管理员如果是超级管理员则绕过所有判断可能有人会问为什么不在角色添加一个名叫超级管理员进行判断因为名称是不可控的在代码逻辑里并不知道用户起的所谓的超级管理员就是我们需要绕过验证的超级管理员假如他叫无敌管理员呢 1 /// summary2 /// 全局的访问权限控制3 /// /summary4 public class GlobalAuthorizeAttribute : System.Attribute, IAuthorizationFilter5 {6 #region 初始化7 private string _currentUrl;8 private string _unauthorizedMessage;9 private readonly Liststring _noCheckPage new Liststring { 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 /// summary2 /// 全局用户权限值查询3 /// /summary4 public class GobalPermCodeAttribute : IActionFilter5 {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.FromJsonAdministratorData();
16 }
17
18
19 public void OnActionExecuting(ActionExecutingContext context)
20 {
21 ((Controller)context.Controller).ViewData[PermCodes] new Listint();
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 : TagHelper3 {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 (((Listint)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 } 视图代码结尾 以上就是我本篇分享的内容项目是以单体应用提供的方案思路也适用于前后端分离。最后附上几个系统效果图 作 者 陈珙出 处https://www.cnblogs.com/skychen1218/p/13053878.html关于作者专注于微软平台的项目开发。如有问题或建议请多多赐教版权声明本文版权归作者和博客园共有欢迎转载但未经作者同意必须保留此段声明且在文章页面明显位置给出原文链接。