通过操作方法属性在MVC3中定义身份验证要求

时间:2012-08-10 05:37:03

标签: asp.net-mvc-3 authentication custom-attributes

我有一个具有4级认证的MVC3应用程序,以及与每个认证相关的4个基本控制器:

  1. 未经身份验证 - BaseController
  2. 用户 - BaseAuthController : BaseController
  3. 顾问 - BaseAdvisorController : BaseAuthController
  4. 管理员 - BaseAdminController : BaseAuthController
  5. 现在我对特殊情况有一系列的覆盖......例如通常仅供管理员使用的控制器可以有一个或两个顾问可以使用的操作方法...我将覆盖定义为数组中的字符串。

    public class BaseAuthController : BaseController
    {
        /// <summary>
        /// Enter action names in here to have them ignored during login detection
        /// </summary>
        public string[] NoAuthActions = new string[] { };
    
        /// <summary>
        /// Actions only usable by Users+
        /// </summary>
        public string[] UserOnlyActions = new string[] { };
    
        /// <summary>
        /// Actions only usable by Advisors+
        /// </summary>
        public string[] AdvisorOnlyActions = new string[] { };
    
        /// <summary>
        /// Actions only usable by Admins+
        /// </summary>
        public string[] AdminOnlyActions = new string[] { };
    
        .......
    
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            //special code here to determine what to do with requested action...
            //verifies that user is logged in and meets requirements for method...
            //if not, redirects out to another page...
        }
    }
    

    在控制器级别,我将它们定义为......

    public class GrowerController : BaseAdminController
    {
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            UserOnlyActions = new string[] { "GrowthStageSelection" };
            AdvisorOnlyActions = new string[] { "Landing", "SeedSelection", "UpdateProjection",
                                                "NitrogenApplications", "DeleteNitrogen", "MassUpload",
                                                "VerifyHolding", "ConfirmHolding", "DeleteHoldingDir", "DeleteHoldingFile" };
            base.OnActionExecuting(filterContext);
        }
    
        //......
    
        [HttpPost]
        public ActionResult GrowthStageSelection(int growerID, int reportGrowthStageID = 0)
        {
            //code...
        }
    }
    

    这个系统实际上对我们来说非常好,但对我来说问题是它感觉很乱。您必须在一个地方定义方法,并在必要时在其他位置覆盖其身份验证级别。如果您更改方法名称,则必须记住在其他位置更改它。

    我能够做的 LOVE 是用特定于身份验证的属性修饰方法本身并取消基于字符串的定义(或至少使它们成为可能透明并动态地使用List<string>或其他东西)。这是我正在寻找的一个例子......

        [HttpPost]
        [AdvisorAuthentication]
        public ActionResult GrowthStageSelection(int growerID, int reportGrowthStageID = 0)
        {
            //code...
        }
    

    问题是我无法通过属性找到实现此目的的好方法。我已尝试创建ActionFilterAttribute的子类,但它们在我BaseAuthController覆盖OnActionExecuting之后运行。此时,在游戏中动态地向字符串列表添加新方法为时已晚,而且我甚至无法从属性中访问当前控制器实例。

    也许整个想法都不合适。谁能指出我正确的方向?感谢。

    最终解决方案

    首先,我继续删除除BaseController之外的所有特殊控制器 - 我再也没用了它们。我将当前的特殊身份验证代码从BaseAuthController移到了BaseController。接下来,我为每个身份验证状态定义了一系列属性:

    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class BaseAuthAttribute : Attribute
    {
        public AuthLevels AuthLevel { get; protected set; }
    
        public BaseAuthAttribute(AuthLevels level)
        {
            this.AuthLevel = level;
        }
    
        public override string ToString()
        {
            return string.Format("Auth Required: {0}", this.AuthLevel.ToString());
        }
    }
    
    public class UnauthenticatedAccess : BaseAuthAttribute
    {
        public UnauthenticatedAccess()
            : base(AuthLevels.Unauthenticated)
        {
        }
    }
    
    public class UserAccess : BaseAuthAttribute
    {
        public UserAccess()
            : base(AuthLevels.User)
        {
        }
    }
    
    public class AdvisorAccess : BaseAuthAttribute
    {
        public AdvisorAccess()
            : base(AuthLevels.Advisor)
        {
        }
    }
    
    public class AdminAccess : BaseAuthAttribute
    {
        public AdminAccess()
            : base(AuthLevels.Admin)
        {
        }
    }
    

    然后在我的BaseController中,我修改了OnActionExecuting,以针对该属性检查登录用户的当前身份验证级别(如果有)。这比之前的 更多 更干净! (注意:SessionUserAuthLevels是我们项目的自定义对象 - 您不会拥有这些对象)

    public partial class BaseController : Controller
    {
        /// <summary>
        /// Override security at higher levels
        /// </summary>
        protected bool SecurityOverride = false;
    
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            BaseAuthAttribute authAttribute = filterContext.ActionDescriptor.GetCustomAttributes(false).OfType<BaseAuthAttribute>().FirstOrDefault();
            if (authAttribute == null) //Try to get attribute from controller
                authAttribute = filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(false).OfType<BaseAuthAttribute>().FirstOrDefault();
            if (authAttribute == null) //Fallback to default
                authAttribute = new UnauthenticatedAccess(); //By default, no auth is required for base controller
    
            if (!SessionUser.LoggedIn
                && authAttribute.AuthLevel == AuthLevels.Unauthenticated)
            {
                SecurityOverride = true;
            }
            else if (SessionUser.LoggedIn
                && SessionUser.LoggedInUser.AuthLevel >= (int)authAttribute.AuthLevel)
            {
                SecurityOverride = true;
            }
    
            if (!SessionUser.LoggedIn && !SecurityOverride)
            {
                //Send to auth page here...
                return;
            }
            else if (!SecurityOverride)
            {
                //Send somewhere else - the user does not have access to this
                return;
            }
    
            base.OnActionExecuting(filterContext);
        }
    
        // ... other code ...
    }
    

    就是这样!现在就这样使用......

    [AdminAccess]
    public class GrowerController : BaseController
    {
        public ActionResult Index()
        {
            //This method will require admin access (as defined for controller)
            return View();
        }
    
        [AdvisorAccess]
        public ActionResult Landing()
        {
            //This method is overridden for advisor access or greater
            return View();
        }
    }
    

2 个答案:

答案 0 :(得分:1)

如果我理解你的问题,你可以实现自己的自定义属性(而不是授权属性),并且在覆盖基本控制器的OnActionExecuting时,你可以检索执行方法的自定义属性,并根据你定义的属性可以采取适当的行动。因此,如果某个方法具有[AdvisorAuthentication],您就知道在继续之前需要检查这些凭据。

修改 我没有一个例子可以指出你,因为这是我在我的一个项目中实现的。我现在无法访问该代码,但这是一个大纲:

protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        IEnumerable<MyCustomAttribute> attributes = filterContext.ActionDescriptor.GetCustomAttributes(false).OfType<MyCustomAttribute>();
        foreach (MyCustomAttributeobj in attributes)
        {
            switch(MyCustomAttribute.AttribType){
                case MyCustomeAttribute.AdvisorAuthentication:

                    break;
                case MyCustomeAttribute.AdminAuthentication:

                    break;
            }
        }
    }

您只能实现一个自定义属性MyCustomAttribute,并让它接受一个参数来指示您想要的授权类型。就像那样使用属性成为[MyCustomAttribute(“MyCustomeAttribute.AdminAuthentication”)]

答案 1 :(得分:0)

您可以创建扩展IAuthorizationFilter and FilterAttribute类似

的不同授权属性
public sealed class AuthenticateAdvisorAttribute : IAuthorizationFilter, FilterAttribute
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        //advisor specific logic goes here
    }
}

public sealed class AuthenticateAdminAttribute : IAuthorizationFilter, FilterAttribute
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        //admin specific logic goes here
    }
}

然后,您可以将这些属性应用于控制器类/操作 如

[AuthenticateAdmin]
public class AdminController : Controller
{

}

[AuthenticateAdvisor]
public class AdvisorController : Controller
{

}