4 つのレベルの認証を備えた MVC3 アプリケーションと、それぞれに関連付けられた 4 つの基本コントローラーがあります。
- 未認証 -
BaseController
- ユーザー -
BaseAuthController : BaseController
- アドバイザー -
BaseAdvisorController : BaseAuthController
- 管理者 -
BaseAdminController : BaseAuthController
現在、特殊なケースのために一連のオーバーライドを用意しています...たとえば、通常は管理者専用のコントローラーに、アドバイザーが使用できる1つまたは2つのアクションメソッドを含めることができます...オーバーライドを配列内の文字列として定義しています。
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...
}
}
このシステムは実際にはうまく機能していますが、私にとっての問題は、それが面倒に感じることです. メソッドを 1 か所で定義し、必要に応じて別の場所で認証レベルをオーバーライドする必要があります。メソッド名を変更する場合は、他の場所で変更することを忘れないでください。
私ができるようにしたいのは、メソッド自体を認証固有の属性で装飾し、文字列ベースの定義を廃止することです (または、少なくともそれらを透明にして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();
}
}