實際上權限系統老早以前我就在一直開發,大概在剛畢業沒多久就想一我的寫一個系統,斷斷續續一直堅持到如今,畢竟本身親動手自寫的系統纔有收穫,本篇僅介紹權限。前端
小小系統上不了檯面,望各位大神勿噴。git
目前採用的是.Net Core微服務的方式實現,本文不討論具體的中間件主要是(ocelot + consul等),一直參考微軟的 eShopOnContainers ,進行簡單的實現,可是ORM是用的Dapper,並簡單進行封裝 傳送門 ,固然本身也封裝了一些簡單的插件進行復用:傳送門,以下:github
權限系統實現很簡單,權限的劃分我以爲能夠分爲三種:數據庫
一、菜單權限二、按鈕權限三、數據權限後端
簡單介紹下:一、菜單權限。表示用戶是否可以訪問該頁面(角色掛鉤)安全
二、按鈕權限。表示用戶是否可以操做該頁面上的功能(角色掛鉤)app
三、數據權限。表示用戶訪問頁面時進行數據篩選(該功能暫未實現,這個要與具體的業務結合才能寫),與部門掛鉤,這個不太好理解,固然通常的權限系統這個功能也不會作,舉個簡單例子,OA系統裏面我查看個人工資條,我應該只能看到我本身的數據,可是個人部門經理,他能夠有權限看到該部門的所有數據,這個就是數據權限。框架
爲何寫這個系統?async
以前待過好幾家公司,發現他們的系統都是對菜單進行分配,固然了,業務需求只要這個就當我沒說,我只是以爲這樣作太不安全而且我以爲以前系統的實現方式能夠進行一些優化,因此就一直寫到如今,可能代碼質量不如哪些大神的優秀,系統在我看來過小,就簡單搭了個框架實現。你過條小水溝,不必造條橋。微服務
要使用該系統前提條件:前端:Sea.js和Vue,對於sea.js,在前端這塊感受已經沒多少人用了,可是這中CMD思想是不會被淘汰的,你看最近比較火的layerui也是的,對於Vues只是簡單的應用,也就用到雙向綁定而已,開發複雜的頁面確實比較方便,可是簡單的頁面就得不償失了。
後端:consul、rabbitmq ,具體怎麼安裝不在描述
大概的用戶訪問流程描述以下:
用戶登陸 =====》 獲取該用戶角色 ===》 經過角色獲取該角色對應的權限 並集 ===>返回相應數據
sys_user_role sys_role_resource
系統關係圖以下(MySQL):
具體功能實現請看代碼,這裏不作闡述,菜單權限的分配經過角色表和菜單表的關聯表操做便可,可是按鈕的權限分配如何實現?個人實現方式是:把按鈕的操做也當作一種菜單的資源分配,只不過比較特殊,我這裏不只僅是對按鈕的顯示進行控制,我作的比較絕,也對後臺方法訪問權限也作了控制,這樣比較安全,對於按鈕權限的控制,其實是明確的,比方說,一個刪除按鈕,它只能對應後臺的一個刪除方法,這個方法是明確的,對於頁面的按鈕的類型和個數是固定的,否則你沒辦法分配,基於這個前提,我對菜單的生成進行代碼控制從而達到控制目的,所以,菜單和按鈕和在一塊兒稱之爲資源表 sys_resource 。具體的實現代碼也不是很複雜,一層一層判斷便可,權限過濾器以下:
1 public class PermissionAuthorizationRequirement : IAuthorizationRequirement 2 { 3 public UrlAndButtonType UrlAndButtonType { get; } 4 5 public PermissionAuthorizationRequirement(string url, ButtonType buttonType, bool isPage) 6 { 7 UrlAndButtonType = new UrlAndButtonType() 8 { 9 Url = url, 10 ButtonType = (byte)buttonType, 11 IsPage = isPage 12 }; 13 } 14 public PermissionAuthorizationRequirement(string url, byte buttonType, bool isPage) 15 { 16 UrlAndButtonType = new UrlAndButtonType() 17 { 18 Url = url, 19 ButtonType = buttonType, 20 IsPage = isPage 21 }; 22 } 23 } 24 /// <summary> 25 /// 權限過濾器 26 /// </summary> 27 [Authorize] 28 [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 29 public sealed class PermissionAttribute : TypeFilterAttribute 30 { 31 /// <summary> 32 /// 構造器 33 /// </summary> 34 /// <param name="url">地址</param> 35 /// <param name="buttonType">按鈕類型</param> 36 /// <param name="isPage">是不是頁面</param> 37 public PermissionAttribute(string url = default(string), ButtonType buttonType = ButtonType.View, bool isPage = true) : 38 base(typeof(RequiresPermissionAttributeExecutor)) 39 { 40 Arguments = new object[] { new PermissionAuthorizationRequirement(url, buttonType, isPage) }; 41 } 42 /// <summary> 43 /// 構造器 44 /// </summary> 45 /// <param name="url">地址</param> 46 /// <param name="buttonType">按鈕類型</param> 47 /// <param name="isPage">是不是頁面</param> 48 public PermissionAttribute(string url, byte buttonType, bool isPage = true) : 49 base(typeof(RequiresPermissionAttributeExecutor)) 50 { 51 Arguments = new object[] { new PermissionAuthorizationRequirement(url, buttonType, isPage) }; 52 } 53 54 private class RequiresPermissionAttributeExecutor : Attribute, IAsyncResourceFilter 55 { 56 private IPermissionStorageContainer _permissionStorage; 57 private PermissionAuthorizationRequirement _requiredPermissions; 58 59 public RequiresPermissionAttributeExecutor( 60 IPermissionStorageContainer permissionStorage, PermissionAuthorizationRequirement requiredPermissions) 61 { 62 _permissionStorage = permissionStorage; 63 _requiredPermissions = requiredPermissions; 64 } 65 66 public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) 67 { 68 string menuUrl = _requiredPermissions.UrlAndButtonType.Url; 69 //判斷用戶權限 70 if (string.IsNullOrEmpty(menuUrl)) 71 { 72 //區域判斷 73 string area = context.RouteData.Values["area"].ToString(); 74 if (string.IsNullOrEmpty(area)) 75 { 76 menuUrl = "/" + context.RouteData.Values["controller"] + "/" + context.RouteData.Values["action"]; 77 } 78 else 79 { 80 menuUrl = "/" + area + "/" + context.RouteData.Values["controller"] + "/" + context.RouteData.Values["action"]; 81 } 82 } 83 menuUrl = menuUrl.Trim().ToLower(); 84 var dbpermission = await _permissionStorage.GetPermissionAsync(); 85 var menu = dbpermission.Menus.FirstOrDefault(m => m.MenuUrl != null && m.MenuUrl.Trim().ToLower() == menuUrl); 86 if (menu != null)//地址存在 87 { 88 if (_requiredPermissions.UrlAndButtonType.ButtonType == default(byte)) 89 { 90 await next(); 91 } 92 else 93 { 94 byte buttonType = (byte)_requiredPermissions.UrlAndButtonType.ButtonType; 95 if (menu.MenuButton.Select(m => m.ButtonType).Contains(buttonType))//擁有操做權限 96 { 97 await next(); 98 } 99 else 100 { 101 //沒有操做權限 102 if (_requiredPermissions.UrlAndButtonType.IsPage) 103 { 104 context.Result = new RedirectResult("/error/noauth"); 105 } 106 else 107 { 108 context.Result = new ContentResult() 109 { 110 Content = PermissionStatusCodes.Status2Unauthorized.ToString() 111 }; 112 } 113 await context.Result.ExecuteResultAsync(context); 114 } 115 } 116 } 117 else 118 { 119 //沒有操做權限 120 if (_requiredPermissions.UrlAndButtonType.IsPage) 121 { 122 context.Result = new RedirectResult("/error/noauth"); 123 } 124 else 125 { 126 context.Result = new ContentResult() 127 { 128 Content = PermissionStatusCodes.Status2Unauthorized.ToString() 129 }; 130 } 131 await context.Result.ExecuteResultAsync(context); 132 } 133 } 134 } 135 136 }
在對於的頁面添加過濾器便可,以下:
1 [HttpGet] 2 [Permission] 3 public async Task<IActionResult> Index(int pageIndex=1,int pageSize=10) 4 { 5 var res = await _messageService.GetPageAsync(pageIndex, pageSize); 6 return View(res); 7 } 8 [HttpGet] 9 [Permission("/Sys/Message/Index", ButtonType.View)] 10 public IActionResult Show() 11 { 12 return View(); 13 }
系統界面展現圖:後臺模板是以前從網上找的並本身簡單改了一下,將就能看吧,實在不想花功夫在前端上面了@-^-@
運行步驟:一、確保數據庫mssystem和mssystemlog存在 github文檔中
二、consul服務啓動,以下回車運行
三、VS項目啓動
管理員登陸帳號wms,密碼:全部帳號密碼都是123
代碼地址:
https://github.com/wangmaosheng/MsSystem-BPM-ServiceAndWebApps
若是以爲有點做用的話,能夠 start 下,後續會持續更新