我们在做项目项目,经常会碰到权限体系,权限体系属于系统架构的一个最底层的功能,也是非常重要的功能,几乎在每个项目都会用到。那么我们该如何设计一个比较合理的且扩展性较强的权限体系呢?
经过多天的摸索,参考多个系统以及自己的经验,《沐雪微店系统 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>(); })
这样就完成了权限控制了。