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



我有一个 MVC3 应用程序,该应用程序具有 4 个身份验证级别,以及每个级别绑定的 4 个基本控制器:

  1. 未经身份验证 - BaseController
  2. 用户 - BaseAuthController : BaseController
  3. 顾问 - BaseAdvisorController : BaseAuthController
  4. 管理员 - BaseAdminController : BaseAuthController

现在,我为特殊情况提供了一系列覆盖...例如,通常仅适用于管理员的控制器可以具有顾问可以使用的一两个操作方法......我将覆盖定义为数组中的字符串。

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...
    }
}

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

我希望能够做的是使用特定于身份验证的属性装饰方法本身,并取消基于字符串的定义(或至少使它们透明并动态使用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();
    }
}

如果我正确理解了您的问题,您可以实现自己的自定义属性(不是授权属性),并且在基本控制器的覆盖 OnActionExecute 中,您可以检索执行方法的自定义属性,并根据定义的自定义属性采取适当的操作。 因此,如果某个方法具有 [顾问身份验证],您就知道您需要在继续之前检查这些凭据。

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

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")]

您可以创建不同的授权属性扩展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
{
}

最新更新