0

我有一个具有 4 个身份验证级别的 MVC3 应用程序,以及与每个级别相关的 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 之外的所有特殊控制器 - 我不再使用它们了。我将当前的特殊身份验证代码BaseAuthControllerBaseController. 接下来,我为每个身份验证状态定义了一系列属性:

[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();
    }
}
4

2 回答 2

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

于 2012-08-10T09:20:49.393 回答
0

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

}
于 2012-08-10T06:07:08.510 回答