本篇专题主要讲述MVC中的权限方案。
权限控制是每个系统都必须解决的问题。
前面的系列文章中我们用到了 SysUser, SysRole, SysUserRole 这几个示例表。
我们以此为基础,完成RBAC (基于角色的控制) 的核心功能。
在此给出我的最佳实践,最终的效果是针对任意一个Action或Controller,都可以根据配置的角色来控制访问权限。
完成此核心功能后,可以再往两方面扩展常用功能:
1. 可以根据 组织/用户/角色 的并集来控制权限
2. 以此核心功能为基础,实现菜单的动态配置
文章提纲
-
概述要点
-
理论基础
-
详细步骤
-
总结
概述要点(知识点)
一、MVC Form认证身份基础
通常用法举例:
1. web.config 》 system.web配置节下,开启form认证
<system.web> <authentication mode="Forms"> <forms loginUrl="~/Account/Login" timeout="10080" cookieless="UseCookies" name="LoginCookieName"></forms> </authentication> </system.web>
2. 需要认证的 Control或Action 上添加过滤,例如限制只有 Scott可以访问
[Authorize(Users = "Scott")] public ActionResult Index() { return View(); }
还有其他两种常用形式,分别表示:
登录用户可以访问
[Authorize]
角色为Admin的用户可以访问
[Authorize(Roles = "Admin")]
过滤条件可以加在Action或整个Controller上。
二、MVC权限过滤器扩展
上述解决方式中很明显会发现有两个缺点:
1. 修改权限时需在Action, Controller上修改后需重新编译,不灵活。
2.过滤器中的Role是内置对象,如果不使用ASP.NET自身的集成权限方案,就无法按照角色来过滤。
解决这两个问题,只需要扩展类AuthorizeAttribute即可。
理论基础
为了能使用自定义的角色控制权限,我们需要扩展或绕过 ASP.NET 的Membership和Role provider 框架。
1.扩展:实现自定义的 Membership/Role provider
2.绕过:直接不使用
我们选择绕过的方式,这样的话更加灵活。
(因为如果你的角色结构和系统不一致,用扩展的方式弄起来比较麻烦)
我们使用form认证的三个核心API, 只用这几个API既可以减少工作量,又可以和Membership/Role provider保持独立,鱼和熊掌兼得。
1. FormsAuthentication.SetAuthCookie
用户登录后,指定用户名
2. Request.IsAuthenticated
登录后返回true
3. HttpContext.Current.User.Identity.Name
返回登录的用户名
权限过滤的完整过程:
1. Authetication ( 登录 )
登录成功后,调用 FormsAuthentication.SetAuthCookie 设置一个用户名。
2. Authorization(授权)
新建自定义的授权属性类:CustomAuthorizeAttribute(继承于AuthorizeAtrribute),扩展权限过滤器
3. 类似于默认Authorize attribute的使用方法,附加自定义的authorize attribute到controller或action上去,实现权限过滤
详细步骤
下面是具体实现步骤。
一、启用form认证,完成登录/退出 基本功能
1. 启用 form 认证
web.config 》 system.web配置节下,启用form认证
<system.web> <authentication mode="Forms"> <forms loginUrl="~/Account/Login" timeout="10080" cookieless="UseCookies" name="LoginCookieName"></forms> </authentication> <compilation debug="true" targetFramework="4.7.2" /> <httpRuntime targetFramework="4.7.2" /> </system.web>
2. 完成登录/退出 基本功能
新建Controller: AccountController
登录功能:
public ActionResult Login() { FormsAuthentication.SignOut(); TempData["ReturnUrl"] = Convert.ToString(Request["ReturnUrl"]); return View(); }
public ActionResult Login(FormCollection fc) { //1.获取表单数据 string userName = fc["UserName"]; string password = fc["Password"]; string email = fc["Email"]; bool remenber = fc["ckbRemenber"]==null?false:true; string returnUrl= Convert.ToString(TempData["ReturnUrl"]); //2.验证用户 SysUser user = work.SysUserRepository.Get(filter: u => u.UserName == userName & u.Password == password).FirstOrDefault(); work.Dispose(); //3.保存票据 if (user!=null) { FormsAuthentication.SetAuthCookie(userName, remenber); if (!string.IsNullOrEmpty(returnUrl)) { return Redirect(returnUrl); } else { return Redirect("~/"); } } else { ViewBag.LoginState = "用户名或密码错误,请重试!"; } return View(); }
退出功能
public ActionResult LoginOut() { FormsAuthentication.SignOut(); return View(); }
二、准备好权限配置文件
1. 用到的基础数据:用户,角色及用户/角色 关系
2. 角色与Action对应的权限关系
这里我们先用一个XML代替,后续最终项目完成时会统一到DB中。
新建文件夹Config,新建ActionRoles文件,配置Action/Role的对应关系
<?xml version="1.0" encoding="utf-8" ?> <Roles> <Controller name="Home"> <Action name="Index">Test</Action> <Action name="About">Manager,Admin,General Users</Action> <Action name="Contact">Admin</Action> </Controller> </Roles>
说明:
Action未配置情况下,默认有访问权限;
Action 配置角色为空,有访问权限。
三、扩展 AuthorizeAttribute
1. 新建类CustomAuthorizeAttribute,继承与AuthorizeAttribute
override两个方法:
a. 在请求授权时调用:
public virtual void OnAuthorization(AuthorizationContext filterContext);
b. 提供一个入口点用于自定义授权检查,通过为true
protected virtual bool AuthorizeCore(HttpContextBase httpContext);
具体实现:
public class CustomAuthorizeAttribute : AuthorizeAttribute { AccountContext db = new AccountContext(); private string[] AuthRoles { get; set; } public override void OnAuthorization(AuthorizationContext filterContext) { // 获取控制器名称 string controlName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; // 获取操作方法名称 string actionName = filterContext.ActionDescriptor.ActionName; // 读取XML string roles = GetXMLRoles.GetActionRoles(actionName, controlName); if (!string.IsNullOrWhiteSpace(roles)) { this.AuthRoles = roles.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); } else { this.AuthRoles = new string[] { }; } base.OnAuthorization(filterContext); } protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException("HttpContext"); } if (AuthRoles == null || AuthRoles.Length == 0) { return true; } if (!httpContext.User.Identity.IsAuthenticated) { return false; } // 1.确定当前角色是否属于指定的角色 string query = @"select RoleName from SysRole where ID in (select SysRoleID from SysUserRole where SysUserID in (select ID from SysUser where UserName=@UserName))"; string currentName = httpContext.User.Identity.Name; SqlParameter[] paras = new SqlParameter[] { new SqlParameter("@UserName",currentName) }; var userRoles = db.Database.SqlQuery<string>(query, paras).ToList(); // 2.验证是否属于AuthRoles foreach (var authRole in AuthRoles) { if (userRoles.Contains(authRole)) { return true; } } return false; } }
以上使用的GetActionRoles的实现:
public class GetXMLRoles { /// <summary> /// 读取XML文件 /// </summary> /// <param name="action"></param> /// <param name="controller"></param> /// <returns></returns> public static string GetActionRoles(string action, string controller) { XElement rootElement = XElement.Load(HttpContext.Current.Server.MapPath("~/Config/") + "ActionRoles.xml"); XElement controlElement = FindElementByAttribute(rootElement, "Controller", controller); if (controlElement != null) { XElement actionElement = FindElementByAttribute(controlElement, "Action", action); if (actionElement != null) { return actionElement.Value; } } return ""; } /// <summary> /// 读取XML文件指定节点 /// </summary> /// <param name="xElement"></param> /// <param name="tagName"></param> /// <param name="attribute"></param> /// <returns></returns> public static XElement FindElementByAttribute(XElement xElement, string tagName, string attribute) { return xElement.Elements(tagName).FirstOrDefault(x => x.Attribute("name").Value.Equals(attribute, StringComparison.OrdinalIgnoreCase)); } }
总结
至此,权限控制的整个过程就OK了,我们来测试一下。
我们把整个HomeController 上都加上 [CustomAuthorize]
使用场景举例:
1. Index 配置为空,任何人都能访问
2. About 配置为Manager, Administrators, General Users 可以访问
未登录时跳转至登录界面
3. Contact配置为 Administrators可以访问
scott的角色为General Users, 不在权限表里,登录不进去此页面
另外补充说明:
如下图,可以设置为全局。
这样就不需要单个设置,对所有Action应用自定义过滤条件
public class FilterConfig { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); //filters.Add(new CustomAuthorizeAttribute()); } }
权责申明
作者:编程小纸条 出处: https://www.cnblogs.com/miro/category/620362.html