2014-02-06 76 views
0

使用ASP.NET MVC 4实现基本授权和身份验证层非常简单;它都是使用“ASP.NET MVC 4 Web应用程序”项目模板自动生成的。在ASP.NET MVC 4中实现'ReAuthentication'属性?

但是,我的任务是实施一些需要重新验证的控制器操作,并且我的目标是提供可维护的解决方案。简单地说,我试图执行以下操作:

  1. 用户登录;
  2. 用户导航到呈现窗体视图的控制器(归因于[Authorize])操作;
  3. 用户通过提交表单来执行POST;
  4. 会出现一个身份验证表单,用户需要使用他/她的用户名和密码重新进行身份验证;
  5. 如果认证成功,继续处理POST请求。

请注意,'重新认证'不必改变当前用户会话的状态。

很明显,有很多方法来实现这个,但我觉得像一个实现类似于下面的(伪)样本会适合我的需要。

[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相关的功能都将被删除或取消激活。

+0

让某人*重新认证*看起来很奇怪,当他们已经通过认证时,您是否正在寻找类似于UAC的东西?我猜想它增加了另一层安全性,但我想可能会让用户感到有点沮丧。你不能用角色来代替吗? – James

+0

你好詹姆斯。您对某个解决方案是否适合_user friendly很有帮助;我真的怀疑我是否会自己决定这样的事情。不幸的是,这些规范决定了这个功能,客户想要它,所以没有办法解决这个问题。另外,不幸的是,角色的使用不会产生影响,因为没有任何影响。不过谢谢你的想法! –

+1

这绝对是可行的,我认为棘手的部分将不会影响当前的身份验证状态。您是否只需重新认证一次,或者每次都需要重新认证? – James

回答

0

我试图实现假设的[ReAuthenticate] -attribute,但发现自己太依赖于反射。把一些思想到一个更易于管理的解决方案后,我终于想出了以下内容:

重新验证类

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类的控制器的对象状态)如果有人有其他建议,请告诉我!

+0

我很高兴你设法找到适合你的解决方案,但是,对我来说,这里有点太多了。就像我在之前的评论中提到的那样,这可以通过使用自定义属性来实现,在我看来,这是一种更简洁的解决方案 - 请参阅我的答案。 – James

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会话变量 - 我认为在AccountControllerLogOn方法将是本

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任何动作正常,并饰以任何行动将被迫每次登录他们要求页面,其中包括页面刷新。

+0

你好詹姆斯,谢谢你的解决方案。与我的相比,我肯定能看到这方面的好处;特别是涉及的代码量。但我确实看到了一个问题:如果它是[HttpPost]标记的模型接受操作,那么执行'SomeSuperSecretAction'会发生什么?它会丢失或必须通过另一个Session变量重新获得,对吧?据我所知,对于“您必须重新进行身份验证才能查看此表单”重新认证方式,但对于“在我们处理您的输入之前重新进行身份验证” - 重新认证方式而言,这非常好。 –

+0

@RobWijkstra这不起作用(至少开箱即可)。问题是当你尝试“POST”时,用户被重定向到登录页面 - 此时POST数据丢失。有办法解决这个问题,然而,你需要做一些自定义的事情,最有可能利用'Session'来存储POST数据,或者使用[307状态码](http://programmers.stackexchange.com/问题/ 99894/why-doesnt-http-have-post-redirect)在重定向期间保留POST数据。 – James