MVC5+EF6 --自定义控制Action访问权限
本章主要讲解在MVC中灵活控制Action的访问权限;
本章所使用的示例表也是上一张所使用的TbUser、TbRole、TbUserRole;
最终的效果是针对任意一个Action或Controller,都可以根据配置的角色来控制访问权限;
完成此核心功能后,可以再往两方面扩展常用功能:
1. 可以根据 组织/用户/角色 的并集来控制权限
2. 以此核心功能为基础,实现菜单的动态配置
PS:扩展功能本章暂时不讲
文章提纲
-
概述
-
理论基础
-
详细步骤
-
总结
概述
一、MVC Form认证身份基础
通常用法举例:
1. web.config配置,开启form认证
2. 需要认证的 Control或Action 上添加过滤,例如限制只有用户"zhangsan"或者角色"管理员"可以访问
另外还有一种常用形式表示:只要有用户登录就可以访问
就是直接在Action或整个Controller上[Authorize]属性过滤
二、为什么需要自定义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,启用form认证
2. 完成登录/退出 基本功能
新建Controller: AccountController.cs
public class AccountController : Controller { private MyDbContext db = new MyDbContext(); // GET: Account public ActionResult Index() { return View(); } public ActionResult Login() { ViewBag.LoginState = "登录前..."; TempData["returnUrl"] = Request["ReturnUrl"]; //如果当前有登录用户,就需要跳转到权限不足提示页面 if (!string.IsNullOrEmpty(User.Identity.Name)) { return View("NoPermissions"); } else { return View(); } } [HttpPost] public ActionResult Login(TbUser user) { string url = Convert.ToString(TempData["returnUrl"]); var userinfo = db.TbUsers.FirstOrDefault(u => u.Email == user.Email && u.Password == user.Password); if (userinfo != null) { FormsAuthentication.SetAuthCookie(userinfo.UserName, false); if (!string.IsNullOrEmpty(url)) { ViewBag.LoginState = userinfo.UserName + "登录后..."; return Redirect(url); } else { return Redirect("~/"); } } else { ViewBag.LoginState = user.Email + "用户不存在..."; } return View(); } public ActionResult Logout() { FormsAuthentication.SignOut(); return Redirect(Request.UrlReferrer.ToString()); } /// <summary> /// 权限不足页面 /// </summary> /// <returns></returns> public ActionResult NoPermissions() { return View(); } }
登录功能:
登出功能:
这里对应的视图文件就不展示了
二、准备好权限配置文件
1. 用到的基础数据:用户,角色及用户/角色 关系
2. 角色与Action对应的权限关系
这里我们先用一个xml代替,理解这个后就可以扩展到DB中。
新建文件夹Config,新建ActionRoles文件,配置Action/Role的对应关系
PS:
Action未配置情况下,默认有访问权限;
Action 配置角色为空,有访问权限。
三、扩展 AuthorizeAttribute
1. 新建类CustomAuthorizeAttribute,继承与AuthorizeAttribute
override两个方法:
在请求授权时调用:
提供一个入口点用于自定义授权检查,通过为true
CustomAuthorizeAttribute.cs(两个方法的具体实现):
public class CustomAuthorizeAttribute : AuthorizeAttribute { private MyDbContext db = new MyDbContext(); /// <summary> /// 对应Action允许的角色 /// </summary> private string[] AuthRoles { get; set; } /// <summary> /// 在请求授权时调用 /// </summary> /// <param name="httpContext"></param> /// <returns></returns> protected override bool AuthorizeCore(HttpContextBase httpContext) { if (httpContext == null) { throw new ArgumentNullException("HttpContext"); } if (AuthRoles == null || AuthRoles.Length == 0) { return false; } #region 确定当前用户角色是否属于指定的角色 //获取当前用户所在角色 string sql = "select RoleName from TbRole where Id in(select roleId " + "from TbUserRole where userid = " + "(select id from TbUser where UserName=@UserName))"; string currentUser = httpContext.User.Identity.Name; SqlParameter[] paras = new SqlParameter[] { new SqlParameter("@UserName",currentUser) }; var userRoles = db.Database.SqlQuery<string>(sql, paras).ToList(); //验证是否属于对应角色 for (int i = 0; i < AuthRoles.Length; i++) { if (userRoles.Contains(AuthRoles[i])) { return true; } } #endregion return false; } /// <summary> /// 提供一个入口点用于自定义授权检查,通过为true /// </summary> /// <param name="filterContext"></param> public override void OnAuthorization(AuthorizationContext filterContext) { string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; string actionName = filterContext.ActionDescriptor.ActionName; //获取config文件配置的action允许的角色,以后可以转到数据库中 string roles = GetActionRoles(actionName, controllerName); if (!string.IsNullOrWhiteSpace(roles)) { this.AuthRoles = roles.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); } else { this.AuthRoles = new string[] { }; } base.OnAuthorization(filterContext); } /// <summary> /// 根据当前Controller和Action名称获取对应节点内容 /// </summary> /// <param name="action">Action名称</param> /// <param name="controller">Controller名称</param> /// <returns></returns> private static string GetActionRoles(string action, string controller) { XElement rootElement = XElement.Load(HttpContext.Current.Server.MapPath("~/Configs/") + "ActionRoles.xml"); XElement controllerElement = FindElementByAttribute(rootElement, "Controller", controller); if (controllerElement != null) { XElement actionElement = FindElementByAttribute(controllerElement, "Action", action); if (actionElement != null) { return actionElement.Value; } } return ""; } private static XElement FindElementByAttribute(XElement xelement, string tagName, string attribute) { XElement nowXelement = xelement.Elements(tagName).FirstOrDefault(e => e.Attribute("name").Value.Equals(attribute, StringComparison.OrdinalIgnoreCase)); return nowXelement; } }
总结
在此,权限控制的整个过程就差不多了,我们来测试一下。
新建HomeController, 新建一些Action做测试(Index, About,Install,Other)
回顾一下基础数据。
HomeController.cs
namespace TestMVC.Controllers { [CustomAuthorizeAttribute] public class HomeController : Controller { // GET: Home public ActionResult Index() { return View(); } public ActionResult About() { return View(); } public ActionResult Install() { return View(); } public ActionResult Other() { return View(); } } }
对应视图:
补充说明:
如下图,可以设置为全局。
这样就不需要单个设置,对所有Action应用自定义过滤条件。