大部分系統都會有權限模塊,別人家系統的權限怎么生成的我不知道,我只知道這樣做是可以並且挺好的。
文章中只對asp.net core
的部分代碼進行說明 呃 記錄~,mvc版本自行前往倉庫查閱
代碼中的一些特性標記后面列出,或前往倉庫查看~
1.根據特性標記生成模塊權限
先上效果圖,感興趣的前往Demo倉庫地址,不感興趣的關閉頁面吧~
模型定義
Demo中菜單分為三級,首先使用枚舉定義模塊,FirstModuleMenu為一級菜單,SecondModuleMenu為二級菜單,三級菜單在action方法上由
PermissionDescription
標識並IsMenu=true
的方法
若是頁面功能則為IsMenu=false
可使用的特性標記還包含以下幾種,並且權限驗證時依次遞增:
- 免登錄:
AllowAnonymous
- 管理員默認權限:
NonePermissionAttribute
- 指定權限:
PermissionDescriptionAttribute
- 依賴權限(包含有這些的任一權限都將獲得授權):
ParentPermissionAttribute
//一級菜單
public class FirstModuleMenu
{
public const string 系統管理 = "系統|icon-setting";
public const string 用戶管理 = "用戶|icon-user";
}
//二級菜單
public enum SecondModuleMenu
{
[Description(FirstModuleMenu.系統管理)]
系統設置,
[Description(FirstModuleMenu.用戶管理)]
用戶管理,
}
//三級菜單
[PermissionDescription(SecondModuleMenu.系統設置, "站點設置", true)]
public ActionResult SiteSetting()
{
return Content("站點設置 System/SiteSetting");
}
生成權限模型集合
定義權限模型 SysModule.cs
調用初始化權限方法
private static List<SysModule> _AllAdminModule { get; set; } = new List<SysModule>();
/// <summary>
/// 初始化權限
/// </summary>
public static void InitPermission()
{
var result = new List<SysModule>();
#region 通過反射讀取Action方法寫入對應權限至集合
//讀取CoreDemo程序集中集成自AdminController的控制器
var types = Assembly.Load("CoreDemo").GetTypes().Where(e => e.BaseType.Name == nameof(AdminController));
var area = "";//默認未使用區域
var now = DateTime.Now;
var i = 1;
foreach (var type in types)
{
//獲取所有action方法
var members = type.GetMethods().Where(e => e.ReturnType.Name == nameof(ActionResult) || e.ReturnType.Name == nameof(IActionResult));
foreach (var member in members)
{
//獲取功能列表
var attrs = member.GetCustomAttributes(typeof(PermissionDescriptionAttribute), true);
if (attrs.Length == 0)
continue;
//功能對應的二級菜單
var parentMenuEnum = (attrs[0] as PermissionDescriptionAttribute).ParentMenu;
var parentMenuName = parentMenuEnum.ToString();
//功能對應的一級菜單 名稱|icon類名
var enumArry = parentMenuEnum.GetEnumDescription().Split('|');
var mainMenuName = enumArry[0];
var existMainMenu = result.Where(e => e.ModuleName == mainMenuName).FirstOrDefault();
if (existMainMenu == null)
{
var mainMenu = new SysModule()
{
Id = i,
ParentId = 0,
ModuleName = mainMenuName,
Icon = enumArry[1] ?? "",
Area = area,
Controller=string.Empty,
Action=string.Empty,
IsMenu = true,
IsVisible = true,
Remark = string.Empty,
DisplayOrder = i,
CreateTime = now
};
i++;
existMainMenu = mainMenu;
existMainMenu.Children = new List<SysModule>();
//添加一級菜單
result.Add(existMainMenu);
}
var existParentMenu = existMainMenu.Children.Where(e => e.ModuleName == parentMenuName).FirstOrDefault();
if (existParentMenu == null)
{
var parentMenu = new SysModule()
{
Id = i,
ParentId = existMainMenu.Id,
ModuleName = parentMenuName,
Icon=string.Empty,
Area = area,
Controller = string.Empty,
Action = string.Empty,
IsMenu = enumArry.Length != 3 || bool.Parse(enumArry[2]),
IsVisible = true,
DisplayOrder = i,
CreateTime = now,
Children = new List<SysModule>()
};
i++;
existParentMenu = parentMenu;
existParentMenu.Children = new List<SysModule>();
existMainMenu.Children.Add(existParentMenu);
//添加二級菜單
result.Add(existParentMenu);
}
var menu = new SysModule()
{
Id = i,
ParentId = existParentMenu.Id,
Area = area,
Action = member.Name,
DisplayOrder = i,
CreateTime = now,
Controller = member.DeclaringType.Name.Substring(0, member.DeclaringType.Name.Length - 10),
IsMenu = (attrs[0] as PermissionDescriptionAttribute).IsMenu,
Children = new List<SysModule>(),
};
if (menu.IsMenu)
menu.IsVisible = true;
menu.ModuleName = (attrs[0] as PermissionDescriptionAttribute).FuncName;
i++;
existParentMenu.Children.Add(menu);
result.Add(menu);
}
}
#endregion
//todo 添加到數據庫
_AllAdminModule = result;
}
2.使用過濾器攔截請求進行驗證
新建特性標記
AdminAuthorizeAttribute
繼承Attribute
類以及實現IAuthorizationFilter
接口的OnAuthorization
方法
不多說,上圖
不多說,上代碼↓_↓
權限驗證過濾器:AdminAuthorizeAttribute
//后台權限驗證
public class AdminAuthorizeAttribute : Attribute,IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext filterContext)
{
//匿名標識 無需驗證
if (filterContext.Filters.Any(e => (e as AllowAnonymous) != null))
return;
var adminInfo = GlobalContext.AdminInfo;//此處應為獲取的登錄用戶
if (adminInfo == null)
{
if(filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
filterContext.Result = new JsonResult("未登錄");
}
else
{
filterContext.Result =new ContentResult() { Content = "未登錄" };
}
return;
}
//對應action方法或者Controller上若存在NonePermissionAttribute標識,即表示為管理員的默認權限,只要登錄就有權限
var isNone = filterContext.Filters.Any(e => (e as NonePermissionAttribute) != null);
if (isNone)
return;
//獲取請求的區域,控制器,action名稱
var area = filterContext.RouteData.DataTokens["area"]?.ToString();
var controller = filterContext.RouteData.Values["controller"]?.ToString();
var action = filterContext.RouteData.Values["action"]?.ToString();
var isPermit = false;
//校驗權限
isPermit = ServiceFactory.CheckAdminPermit(adminInfo.Id, area, controller, action);
if (isPermit)
return;
//此action方法的父輩權限判斷,只要有此action對應的父輩權限,皆有權限訪問
var pAttrs = filterContext.Filters.Where(e => (e as ParentPermissionAttribute) != null).ToList();
if (pAttrs.Count > 0)
{
foreach (ParentPermissionAttribute pattr in pAttrs)
{
if (!string.IsNullOrEmpty(pattr.Area))
area = pattr.Area;
isPermit = ServiceFactory.CheckAdminPermit(adminInfo.Id, area, pattr.Controller, pattr.Action);
if (isPermit)
return;
}
}
if (!isPermit)
{
filterContext.Result = new ContentResult() { Content = "無權限訪問" };
return;
}
}
}
自定義特性標記,用於權限校驗
此處的自定義的特性標記不能繼承Attribute
,因無法在AdminAuthorizeAttribute中的上下文filterContext.Filters
中獲取到特性標記(不知道咋取特性標記,所以用這種方式代替,也更為簡單 冏)
!!!!!!!!!修改: 之前腦袋沒有轉過彎來,要使過濾器上下文的Filters中發現自定義過濾器需要繼承 Attribute, IFilterMetadata
/// <summary>
/// 管理員的默認權限
/// </summary>
public class NonePermissionAttribute : Attribute, IFilterMetadata{}
/// <summary>
/// 匿名驗證
/// </summary>
public class AllowAnonymous : Attribute, IFilterMetadata{}
/// <summary>
/// 長輩權限
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class ParentPermissionAttribute : Attribute, IFilterMetadata
{
/// <summary>
/// 區域
/// </summary>
public string Area { get; set; }
/// <summary>
/// 控制器
/// </summary>
public string Controller { get; set; }
/// <summary>
/// Action名稱
/// </summary>
public string Action { get; set; }
public ParentPermissionAttribute(string area, string controller, string action)
{
this.Area = area;
this.Controller = controller;
this.Action = action;
}
public ParentPermissionAttribute(string controller, string action)
{
this.Controller = controller;
this.Action = action;
}
}
若將代碼全部貼出,有點略顯多余,故,只貼出了部分核心代碼.其他一些模型,擴展 請直奔倉庫地址...
或使用git命令克隆MvcPermission
分支到MvcPermission文件夾:git clone https://git.coding.net/yimocoding/WeDemo.git -b MvcPermission
補充
- 2017-09-29
突然靈光一現,將文中的ResultFilterAttribute
特性標記替換為Attribute, IFilterMetadata
,果然可以~
故得出:實現了IFilterMetadata
的特性標記能夠在過濾器的上下文中獲取到。