我們在做項目項目,經常會碰到權限體系,權限體系屬於系統架構的一個最底層的功能,也是非常重要的功能,幾乎在每個項目都會用到。那么我們該如何設計一個比較合理的且擴展性較強的權限體系呢?
經過多天的摸索,參考多個系統以及自己的經驗,《沐雪微店系統 NetCore3.1》的權限體系是這樣的。
- 一、首先確定幾個重要實體的關系:用戶,角色,權限;這三者之間的關系如下:
其中:
1、用戶與角色是1對多關系( 1個用戶只有1個角色,1個角色可以對應多個用戶);
2、角色與權限組是1對1關系( 1個角色只有1個權限組,1個權限組只有1個角色)。
3、一個權限組里包含1個菜單和多個操作按鈕;
4、操作按鈕預先定義好最多的情況的枚舉值;(比如 查看,新增,修改,刪除,審核,下載,確認,回復)
這樣的架構,相對來說比較合理,適合絕大多數系統使用了。(設計的越靈活,控制起來越困難,越困難越容易出現錯誤,所以要根據實際的情況控制靈活到什么程度即可;不要一口吃成胖子,想要一次性搞出無限靈活的權限系統。)
- 二、對幾個實體進行CRUD單實體操作(增,刪,改,查)
1、菜單的增刪改查;
2、權限組的增刪改查:
3、用戶的增刪改查:
- 三、用代碼來實現權限系統
大家在菜單管理頁面,應該注意到有3個字段:--編碼,鏈接地址和權限值;這些是我們寫代碼的時候需要用的。
1、創建一個控制器的父類--BaseController,這里只要有一個可以獲取當前登錄者的方法即可,類似如下代碼:

/// <summary> /// 當前登錄者 /// </summary> public PTLoginResp CurrentUser { get { PTLoginResp currentUser = new PTLoginResp(); if (User.Identity.IsAuthenticated) { var claimIdentity = (ClaimsIdentity)User.Identity; string key = claimIdentity.FindFirst("tokenid").Value; currentUser.id = LoginCredentials.PFDecodeRedisKeyOfUserId(key); currentUser.user_name = claimIdentity.FindFirst("user_name").Value; currentUser.real_name = claimIdentity.FindFirst("real_name").Value; currentUser.mobile_phone = claimIdentity.FindFirst("mobile_phone").Value; currentUser.role_id = ConvertHelper.LongParse(claimIdentity.FindFirst("role_id").Value, 0); } return currentUser; } }
2、創建具體的業務控制器和相應的Action,並且集成BaseController;
3、創建一個Action的權限屬性特性方法,比如下面代碼:
/// <summary> /// Action的權限屬性 /// </summary> [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class ActionAuthDescriptorAttribute : System.Attribute { public ActionAuthDescriptorAttribute(string NavCode, AuthTypeEnum AuthType) { this.NavCode = NavCode; this.AuthType = AuthType; } /// <summary> /// 權限組code /// </summary> public string NavCode { get; set; } /// <summary> /// 權限操作的枚舉值,比如Show /// </summary> public AuthTypeEnum AuthType { get; set; } }
這樣,我們就可以在Action上加上這個特性了:
/// <summary> /// 《沐雪微店系統 Netcore 3.1》添加管理員頁面 /// </summary> /// <returns></returns> [ActionAuthDescriptor("manager_mgr", AuthTypeEnum.Add)] public async Task< ActionResult> Create() { ManagerInfo managerAdd = new ManagerInfo(); managerAdd.using_type = UsingTypeEnum.sys.ToString(); managerAdd.is_lock = false; List<muxue_role> roleList =await _roleService.GetRoleList(UsingTypeEnum.sys); roleList = roleList.FindAll(p => p.is_sys == false); ViewBag.roleList = roleList; return View(managerAdd); }
4、開始寫權限驗證過濾器了PrivilegeFilter:
驗證的大概邏輯如下:
/// <summary> /// 《沐雪微店系統 Netcore 3.1》權限驗證邏輯 /// (集成BaseController的控制器,需要登錄后才可以) /// 1、看下Controller是否有AllRightsAttribute; /// 2、看下Action是否有AllRightsAttribute,是否有ActionAuthDescriptorAttribute /// 3、(1)若Action有AllRightsAttribute,則說明任何登錄者都可以訪問; /// (2)若Action沒有AllRightsAttribute,但是Controller上有,則說明任何登錄者都可以訪問; /// (3)若Action和Controller都沒有,並且Aciton也沒有ActionAuthDescriptorAttribute, 則任何人都不可以訪問; /// 4、看下Action是否有ActionAuthDescriptorAttribute,則任何人都不可以訪問; /// 5、若有ActionAuthDescriptorAttribute,則進行判斷該Action是否有該角色的權限; /// </summary> /// <param name="context"></param>
重點的代碼如下:
public override void OnActionExecuting(ActionExecutingContext context) { var controller = context.Controller as BaseController; if (controller == null) { base.OnActionExecuting(context); return; } if (controller.CurrentUser == null) { //《沐雪微店系統 Netcore 3.1》去登錄 // context.HttpContext.ChallengeAsync().Wait(); base.OnActionExecuting(context); return; } string requestMethod = context.HttpContext.Request.Method.ToLower(); ControllerActionDescriptor ad = context.ActionDescriptor as ControllerActionDescriptor; bool isControllerAllRights = ad.ControllerTypeInfo.IsDefined(typeof(AllRightsAttribute), false); bool isActionAllRights = ad.MethodInfo.IsDefined(typeof(AllRightsAttribute), false); bool isActionAuthDescriptor = ad.MethodInfo.IsDefined(typeof(ActionAuthDescriptorAttribute), false); if (!isControllerAllRights && !isActionAllRights && !isActionAuthDescriptor) { //沒有權限訪問 if (requestMethod == "get") { context.Result = new RedirectResult("/Error/Index?msg=沒有權限"); base.OnActionExecuting(context); return; } else { context.Result = new JsonResult(NoPrivPostJsonResult()); base.OnActionExecuting(context); return; } } if (isActionAllRights) { base.OnActionExecuting(context); return; } if (isControllerAllRights && !isActionAllRights && !isActionAuthDescriptor) { base.OnActionExecuting(context); return; } if (isActionAuthDescriptor) { var authorizeAttr = ad.MethodInfo.GetCustomAttributes(typeof(ActionAuthDescriptorAttribute), false).FirstOrDefault() as ActionAuthDescriptorAttribute; string navCode = authorizeAttr.NavCode; AuthTypeEnum authType = authorizeAttr.AuthType; long current_role_id = controller.CurrentUser.role_id; bool hasRolePriv = _roleprivilegeService.HasRolePriv(current_role_id, navCode, authType).Result; if (!hasRolePriv) {//沒有權限 if (requestMethod == "get") { context.Result = new RedirectResult("/Error/Index?msg=沒有權限"); base.OnActionExecuting(context); return; } else { context.Result = new JsonResult(NoPrivPostJsonResult()); base.OnActionExecuting(context); return; } } } base.OnActionExecuting(context); }
將這個Filter添加到StartUp里:
services.AddMvc(options => { if (!env.IsDevelopment()) { } options.Filters.Add<LogstashFilter>(); options.Filters.Add<PrivilegeFilter>();//權限驗證 options.Filters.Add<XcActionFilter>(); options.Filters.Add<GlobalExceptions>(); })
這樣就完成了權限控制了。