MVC+EF6教程六:灵活控制Action权限

本篇专题主要讲述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());
        }
    }

猜你喜欢

转载自www.cnblogs.com/qianj/p/12516879.html