在ASP.NET MVC 4中实现'ReAuthentication'属性?

时间:2014-02-06 13:24:33

标签: asp.net-mvc asp.net-mvc-4

ASP.NET MVC 4实现基本授权和身份验证层非常简单;它全部使用“ASP.NET MVC 4 Web应用程序”项目模板自动生成。

但是,我的任务是实现一些需要重新认证的控制器操作,我的目标是提供可维护的解决方案。简单地说一下用户故事,我正在尝试实现以下内容:

  1. 用户登录;
  2. 用户导航到控制器(归属于[授权])操作,该操作呈现表单视图;
  3. 用户通过提交表单执行POST;
  4. 出现一个身份验证表单,用户需要使用他/她的用户名和密码重新进行身份验证;
  5. 如果验证成功,请继续处理POST请求。
  6. 请注意,'reauthentication'不必改变当前用户会话的状态。

    显然,有很多方法可以实现这一点,但我觉得看起来像下面(伪)样本类似的实现符合我的需要。

    [Authorize]
    [InitializeSimpleMembership]
    public class SpecialActionController : Controller
    {
        public ActionResult SpecialForm() { return View(); }
        public ActionResult Succes() { return View(); }
    
        [HttpPost]
        [ReAuthenticate] /* <- Prompts user with reauthentication form before proceeding. */
        public ActionResult SpecialForm(SpecialFormModel model)
        {
            if (ModelState.IsValid)
                RedirectToAction("Succes");
            else
                return View(model);
        }
    
    }
    

    有什么建议吗?

    编辑:我忘了提及任何与OAuth相关的功能都超出了范围。外部身份验证不是问题,不需要支持。事实上,在我正在开发的当前项目中,所有与OAuth相关的功能都将被删除或停用。

2 个答案:

答案 0 :(得分:1)

您应该可以使用自定义AuthorizeAttributeSession的组合来完成此操作。覆盖AuthorizeCore方法并允许进行所有默认身份验证,但引入您自己的额外检查(用于重新身份验证),例如。

public class RecurringAuthorizeAttribute : AuthorizeAttribute  
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        var reauth = (bool?)httpContext.Session["ReAuthenticated"];
        var result = base.AuthorizeCore(httpContext) && (reauth ?? false);
        httpContext.Session["ReAuthenticated"] = !result;
        return result;
    }
}

这应该在用户每次点击操作时将用户重定向到登录页面,并且他们没有重新进行身份验证。如果用户已重新进行身份验证,我们会清除会话变量以强制登录下一个请求。

为了使其正常工作,我们需要一个钩子来设置ReAuthentication会话变量 - 我认为LogOn中的AccountController方法将是此

public class AccountController : Controller
{
    ...
    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid && WebSecurity.Login(model.UserName, model.Password, persistCookie: model.RememberMe))
        {
            Session["ReAuthenticated"] = User.Identity.IsAuthenticated;
            return RedirectToLocal(returnUrl);
        }
        ...
    }
}

然后剩下要做的就是装饰我们的控制器动作

[Authorize]
public ActionResult SomePrivateAction()
{
   ...
}

[RecurringAuthorize]
public ActionResult SomeSuperSecretAction()
{
   ...
}

对于使用默认AuthorizeAttribute的任何操作,您应该会发现授权将正常工作,并且每次}的任何操作都会被强制登录他们请求页面,其中包括页面刷新。

答案 1 :(得分:0)

我试图实现假设的[ReAuthenticate] - 属性,但发现自己过于依赖反射。在考虑了一个更易于管理的解决方案之后,我终于想出了以下内容:

ReAuth类

public sealed class ReAuth
{
    #region Constructor

    private ReAuth(Func<System.Web.Mvc.ActionResult> onSuccessAction)
    {
        this.onSuccessAction = onSuccessAction;
    }

    #endregion

    #region Public static members

    public static ReAuth CreateFor(HttpSessionStateBase httpSession, Func<System.Web.Mvc.ActionResult> onSuccessAction)
    {
        httpSession[sessionKey] = new ReAuth(onSuccessAction);
        return GetFor(httpSession);
    }

    public static ReAuth GetFor(HttpSessionStateBase httpSession)
    {
        return httpSession[sessionKey] as ReAuth;
    }

    public static bool ExistsFor(HttpSessionStateBase httpSession)
    {
        return httpSession[sessionKey] as ReAuth != null;
    }

    #endregion

    #region Public instance members

    public bool ReAuthenticated { get; set; }

    public System.Web.Mvc.ActionResult Handle()
    {
        if (ReAuthenticated)
            return onSuccessAction();
        else
            return new System.Web.Mvc.RedirectToRouteResult(
                new System.Web.Routing.RouteValueDictionary 
                {
                    { "Controller", "#" }, /* Replace '#' with the name of the controller that implements the re-authentication form... */
                    { "Action", "#" } /* Replace '#' with the name of the action on the aforementioned controller. */
                });
    }

    #endregion

    #region Private members

    private const string sessionKey = "reAuthenticationSessionKey";

    private readonly Func<System.Web.Mvc.ActionResult> onSuccessAction;

    #endregion
}

<强>实施

假设我们有一个假设的控制器,其中应用了解决方案:

public class AccountInfoController : System.Web.Mvc.Controller
{
    /* snip... */

    [HttpPost]
    public ActionResult EditAccountInfo(AccountInfo model)
    {
        if (ModelState.IsValid)
            return ReAuth.CreateFor(Session, () => { return Success(); }).Handle();
        else
            return View(model);
    } 
}

...而且,我们需要一个控制器(实质上是真正的 AccountController的副本,它不会篡改表单身份验证用户会话状态),其中重新认证发生。:

public class ReAuthController : System.Web.Mvc.Controller
{
    /* Snip... */
    [HttpPost]
    public ActionResult LogOn(LogOnModel model)
    {
        if (ModelState.IsValid)
        {
            ReAuth.GetFor(Session).ReAuthenticated = Membership.ValidateUser(model.User, model.Password);
            return ReAuth.Handle();
        }
        return View(model);
    }
}

据我所知,这是一个可管理的解决方案。它确实依赖于将对象存储到会话状态。 (特别是实现ReAuth类的控制器的对象状态)如果有人有其他建议,请告诉我!