在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和权限代码不能随便更改,当然,可以用一个常量类来保存我们的模块和权限,但是总的上来说,还可以将就使用的。