你的位置:首页 > ASP.net教程

[ASP.net教程]MVC5+EF6


本章主要讲解在MVC中灵活控制Action的访问权限;

本章所使用的示例表也是上一张所使用的TbUser、TbRole、TbUserRole;

最终的效果是针对任意一个Action或Controller,都可以根据配置的角色来控制访问权限;

完成此核心功能后,可以再往两方面扩展常用功能:

1. 可以根据 组织/用户/角色 的并集来控制权限

2. 以此核心功能为基础,实现菜单的动态配置

PS:扩展功能本章暂时不讲

文章提纲

  • 概述
  • 理论基础
  • 详细步骤
  • 总结

概述

一、MVC Form认证身份基础

通常用法举例:

1. web.config à system.web配置节下,开启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();    }  }

View Code

登录功能:

登出功能:

这里对应的视图文件就不展示了

二、准备好权限配置文件

1. 用到的基础数据:用户,角色及用户/角色 关系

2. 角色与Action对应的权限关系

这里我们先用一个

新建文件夹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.");      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;    }  }

View Code

 

总结

在此,权限控制的整个过程就差不多了,我们来测试一下。

新建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();    }  }}

View Code

对应视图:

 

补充说明:

如下图,可以设置为全局。

这样就不需要单个设置,对所有Action应用自定义过滤条件。