我有一个具有 4 个身份验证级别的 MVC3 应用程序,以及与每个级别相关的 4 个基本控制器:
- 未经身份验证 -
BaseController
- 用户 -
BaseAuthController : BaseController
- 顾问 -
BaseAdvisorController : BaseAuthController
- 行政 -
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
,根据属性检查登录用户的当前身份验证级别(如果有的话)。这比以前干净多了!(注意:SessionUser
和AuthLevels
是我们项目的自定义对象 - 你不会拥有那些)
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();
}
}