在MVC中,我們可以通過在action或者controller上設置Authorize[Role="xxx"] 的方式來設置用戶對action的訪問權限。顯然,這樣並不能滿足我們的需求,
對於一般的MVC系統來說,如果我們定義一個controller來處理一個模塊的話,我們大致有以下需求:
一,單個action的訪問權限。如果刪除,列表action
二,一個action兩種權限,如edit(int? id)如果id為null則添加,或者修改
三,在此cotroller驗證其它模塊權限,比如,我們要在新聞模塊獲取新聞列表
四,對於某些通過模塊,如分類,我們希望通過傳入不同的參數可以驗證不同模塊的權限
對於四種情況,我理想的想法是:
對於第一種,直接制定controller的moduleid和action的權限
[Module(ModuleId=6)] public class Controller: Controller{ [SysAuthorize(Permission.List)] //設置action要驗證的權限 public ActionResult List() }
對於第二種情況,我們希望通過參數來達到驗證那個權限的目的:
[Module(ModuleId=6)] public class Controller: Controller{ //如果參數為null是將驗證添加權限否則驗證修改權限 [SysAuthorize(Permission.Add,Permission.Edit,"id",null)] public ActionResult Edit(int? id) }
對於第三種情況,我們可以為action驗證指定單獨的模塊id
[Module(ModuleId=6)] public class Controller: Controller{ [SysAuthorize(9,Permission.List)] //此方面驗證模塊9的列表權限 public ActionResult List(int CType) }
對於第四種情況,我們可以為模塊添加不同的參數module對應關系
[Module(ModuleId=5,"CType",1)] [Module(ModuleId=6,"CType",2)] public class Controller: Controller{ 如果當前傳入CType為1則驗證ModuleId=5,等於2是驗證ModuleId=6 [SysAuthorize(Permission.List)] public ActionResult List(int CType) }
想法定好以后,我們就可以去實現了。
首先,我們定義一個module的特性:
/// <summary> /// 模塊信息特性 /// </summary> [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] public class ModuleAttribute : Attribute { public ModuleAttribute() { } public ModuleAttribute(int moduleId) { this.ModuleId = moduleId; } public ModuleAttribute(int moduleId, string parName, object value) : this(moduleId) { this.ParameterName = parName; this.ParameterValue = value; } /// <summary> /// 模塊Id /// </summary> public int ModuleId { get; set; } /// <summary> /// 當前模塊對應參數名 /// </summary> public string ParameterName { get; set; } /// <summary> /// 當前模塊參數值 /// </summary> public object ParameterValue { get; set; } /// <summary> /// 驗證參數值是否有正確 /// </summary> public bool CheckParameter(HttpRequestBase request) { var val = request[ParameterName]; bool b = false; if (val == null && ParameterValue == null) b = true; else if (val != null && ParameterValue != null && val == ParameterValue.ToString()) b = true; return b; } }
實現了模塊特性以后,就是比較重要的驗證特性了:
/// <summary> /// 系統權限驗證 /// <remarks> /// 0,只驗證登陸:無參數時,只驗證登陸 /// 1,單一:只一指定權限代碼時驗證當前模塊的指定權限 /// 2,二選一:指定兩種權限代碼時,根據參數驗證,如果參數等於指定參數,則驗證前者,否則驗證后者 /// </remarks> /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] public class SysAuthorizeAttribute : AuthorizeAttribute { /// <summary> /// 驗證是否登陸 /// </summary> public SysAuthorizeAttribute() { } /// <summary> /// 驗證基本權限 /// </summary> public SysAuthorizeAttribute(Permission permission) { this.PermissionFlag = permission; } /// <summary> /// 驗證基本權限 /// </summary> public SysAuthorizeAttribute(int moduleId, Permission permission) : this(permission) { this.ModuleId = moduleId; this.IsSetModuleId = true; } /// <summary> /// 帶參數的驗證 /// </summary> public SysAuthorizeAttribute(Permission permission, string parName, object value = null) { this.PermissionFlag = permission; this.ParameterName = parName; this.ParameterValue = value; } /// <summary> /// 帶參數的驗證 /// </summary> public SysAuthorizeAttribute(int moduleId, Permission permission, string parName, object value = null) : this(permission, parName, value) { this.ModuleId = moduleId; this.IsSetModuleId = true; } /// <summary> /// 帶參數的驗證二選一 /// </summary> public SysAuthorizeAttribute(Permission before, Permission after, string parName, object value = null) { this.PermissionFlag = before; this.PermissionFlag1 = after; this.ParameterName = parName; this.ParameterValue = value; } /// <summary> /// 帶參數的驗證二選一 /// </summary> public SysAuthorizeAttribute(int moduleId, Permission before, Permission after, string parName, object value = null) : this(before, after, parName, value) { this.ModuleId = moduleId; this.IsSetModuleId = true; } /// <summary> /// 當前要驗證的權限代碼 /// </summary> private Permission? PermissionFlag { get; set; } /// <summary> /// 當前要驗證的另一個權限代碼(當二選一驗證驗證方式時有效) /// </summary> private Permission? PermissionFlag1 { get; set; } /// <summary> /// 是否自定義設置了moduleId /// </summary> private bool IsSetModuleId { get; set; } /// <summary> /// 獲取或設置當前模塊Id /// </summary> public int? ModuleId { get; set; } /// <summary> /// 權限驗證參數名 /// </summary> public string ParameterName { get; set; } /// <summary> /// 權限驗證參數值 /// </summary> public object ParameterValue { get; set; } /// <summary> /// 驗證結果 /// </summary> public bool AuthorizeResult { get; private set; } /// <summary> /// 驗證前獲取moduleId /// </summary> public override void OnAuthorization(AuthorizationContext filterContext) { if (!IsSetModuleId) { var modules = filterContext.Controller.GetModules(); //一個模塊的的時候,只第一次進入時獲取他的模塊id,緩存以后不作處理 if (modules.Count == 1 && ModuleId == null) { if (!string.IsNullOrWhiteSpace(modules[0].ParameterName)) { if (modules[0].CheckParameter(filterContext.HttpContext.Request)) ModuleId = modules[0].ModuleId; } else ModuleId = modules[0].ModuleId; } //多個模塊的時候,每次驗證強制更新及moduleid else if (modules.Count > 1) { foreach (var m in modules) { if (m.CheckParameter(filterContext.HttpContext.Request)) { ModuleId = m.ModuleId; break; } } } } base.OnAuthorization(filterContext); } /// <summary> /// 核心驗證 /// </summary> protected override bool AuthorizeCore(HttpContextBase httpContext) { //如果未登陸,則跳轉到登陸頁 if (!httpContext.User.Identity.IsAuthenticated) httpContext.Response.Redirect(FormsAuthentication.LoginUrl); AuthorizeResult = true; if (PermissionFlag != null) { if (PermissionFlag.Value == Permission.Administrator) return AdminSiteService.CheckAdministrator(); //未設置模塊id,則拋出異常 if (ModuleId == null) throw new Exception(string.Format("未設置模塊id的Control不能進行權限驗證!")); //處理二選一 if (PermissionFlag1 != null) { if (string.IsNullOrWhiteSpace(ParameterName)) throw new Exception(string.Format("請為二選一驗證指定相應的參數名!")); //如果參數值等於給定值,則驗證前者,否則驗證后者 if (CheckParameter(httpContext.Request)) AuthorizeResult = AdminSiteService.CheckPermission(ModuleId.Value, (int)PermissionFlag.Value); else AuthorizeResult = AdminSiteService.CheckPermission(ModuleId.Value, (int)PermissionFlag1.Value); } else //一般驗證處理 { //如果參數名不為空,則先驗證參數值是否匹配 if (!string.IsNullOrWhiteSpace(ParameterName)) AuthorizeResult = CheckParameter(httpContext.Request); if (AuthorizeResult) AuthorizeResult = AdminSiteService.CheckPermission(ModuleId.Value, (int)PermissionFlag.Value); } } return AuthorizeResult; } /// <summary> /// 錯誤處理 /// </summary> protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { filterContext.Result = new RedirectResult("~/Main/Error?code=100"); } /// <summary> /// 驗證參數值是否有正確 /// </summary> private bool CheckParameter(HttpRequestBase request) { var val = request[ParameterName]; bool b = false; if (val == null && ParameterValue == null) b = true; else if (val != null && ParameterValue != null && val == ParameterValue.ToString()) b = true; return b; } }
注意AuthorizeAttribute緩存問題。第一訪問時會緩存該Action的身份認證類,所以多模塊驗證時需要重新獲取moduleId
如果當前AuthorizeAttribute沒有指定moduleid,則每次訪問強制更新其moduleId
補充一下獲取控制器模塊的擴展方法:
/// <summary> /// 獲取控制器相關模塊 /// </summary> public static List<ModuleAttribute> GetModules(this ControllerBase controller, bool useCache = true) { if (controller == null) return null; string cacheKey = string.Format("{0}_Modules", controller.GetType().Name); if (useCache) { if (CacheProvider.Cache.Contains<List<ModuleAttribute>>(cacheKey)) return CacheProvider.Cache.Get<List<ModuleAttribute>>(cacheKey); } var moduleInfos = controller.GetType().GetCustomAttributes(typeof(ModuleAttribute), false); List<ModuleAttribute> modules = new List<ModuleAttribute>(); if (moduleInfos.Length <= 0) return modules; foreach (var m in moduleInfos) { modules.Add((ModuleAttribute)m); } if (useCache) //緩存控制器模塊信息 CacheProvider.Cache.Add<List<ModuleAttribute>>(cacheKey, modules, 20); return modules; }
驗證方法主要是幫我們區分出是驗證哪一個模塊的哪一個權限,最后把模塊id和權限標識傳入我們的邏輯層進行驗證,我們可以在登陸的時候緩存用戶的模塊權限。
驗證大致代碼:
/// <summary> /// 判斷當前登陸用戶對操作是否有權限 /// </summary> public static bool CheckPermission(int ModuleId, int permissionFlag) { //FormsAuthentication.GetAuthCookie() var user = HttpContext.Current.User; //未登陸的用戶 if (!user.Identity.IsAuthenticated) return false; AdminInfo info = GetLoginAdminInfo(); //超級管理員有所有權限 if (info.RoleId == Constant.AdministratorRoleId) return true; if (!info.ModulePermissions.Exists(t => t.AdminId == info.AdminId && t.ModuleId == ModuleId && t.PermissionFlag == permissionFlag)) return false; return true; }
最后,我們就可以在我們的系統中使用了:
using FL.Entitys; using FL.Site.Service; using FL.Site.SysManager.Common; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using FL.Site.ViewModel; using FL.Site.SysManager.JUI; namespace FL.Site.SysManager.Controllers { [Module(ModuleId = 7)] public class AdminController : BaseController<AdminSiteService> { /// <summary> /// 分頁列表 /// </summary> [SysAuthorize(Permission.List)] public ActionResult List(PagerPostItem postPager) { var pager = new PagerItem(postPager, TargetType.NavTab); int recordCount; var list = Service.GetPageList(pager.currentPage, pager.numPerPage, out recordCount); pager.totalCount = recordCount; var roles = new RoleSiteService().GetRoles(); ViewBag.Roles = roles; return View(list, pager); } /// <summary> /// 編輯輸入 /// </summary> [SysAuthorize(Permission.Add, Permission.Update, "id")] public ActionResult Edit(int? id) { var entity = new AdminSaveModel(); if (id != null) entity = Service.GetById(id.Value); var roles = new RoleSiteService().GetRoles(); ViewBag.Roles = new SelectList(roles, "RoleId", "RoleName", entity.RoleId); return View(entity); } /// <summary> /// 保存數據 /// </summary> [HttpPost] [SysAuthorize(Permission.Add, Permission.Update, "AdminId", 0)] public ActionResult Edit(AdminSaveModel entity) { entity.LastUpdateTime = DateTime.Now; entity.LastUpdateAdmin = UserInfo.LoginName; if (ModelState.IsValid) return Json(Service.Save(entity)); else return Json(AjaxResult.NewModelCheckErrorResult(ModelState)); } /// <summary> /// 刪除指定id的數據 /// </summary> [SysAuthorize(Permission.Delete)] public ActionResult Delete(int id,int roleId) { return Json(Service.Delete(id, roleId), false); } /// <summary> /// 修改密碼 /// </summary> [SysAuthorize(Permission.Update)] public ActionResult EditPwd(int id) { ViewBag.Id = id; return View(); } [HttpPost] [SysAuthorize(Permission.Update)] public ActionResult EditPwd(string Pwd, string ConfirmPwd, int id) { return Json(Service.UpdatePassword(Pwd, ConfirmPwd, id)); } /// <summary> /// 獲取用戶登陸日志 /// </summary> [SysAuthorize(22, Permission.List)] public ActionResult LoginLog(PagerPostItem postPager) { var pager = new PagerItem(postPager, TargetType.NavTab); int recordCount; var list = Service.GetLoginLogPageList(pager.currentPage, pager.numPerPage, out recordCount); pager.totalCount = recordCount; return View(list, pager); } } }
很久沒寫文章,有點小亂,也沒提供代碼及數據庫相關的東西,也算是分享一種相法吧。程序一個人寫久了很寂寞
數據庫大致結構:模塊表,角色表,用戶表,模塊權限表,角色模塊權限表,用戶角色表, 另外寫一個視圖獲取用戶的模塊權限,在登陸的時候緩存,就可以使用上面的方式驗證了。
總結:在基於模塊的權限驗證系統中,只需要為程序提供模塊,登陸用戶及要驗證的權限,就可以非常方便的驗證用戶權限。上面的一系列代碼都是為了取得我們想要的模塊id和權限
缺點:模塊id和權限代碼不能隨便更改,當然,可以用一個常量類來保存我們的模塊和權限,但是總的上來說,還可以將就使用的。
