一种在MVC 4应用程序中正确处理HttpAntiForgeryException的方法

时间:2012-10-19 05:30:32

标签: c# asp.net-mvc antiforgerytoken

以下是该方案:

我有一个登录页面,当用户签名时,它会被重定向到主页应用程序页面。然后用户使用浏览器后退按钮,现在他在登录页面上。他试图再次登录,但现在抛出异常:

  

HttpAntiForgeryException(0x80004005):提供的防伪令牌适用于用户“”,但当前用户是“userName”。

我知道这与缓存有关。我使用自定义NoCache过滤器禁用了浏览器缓存以进行登录操作,该过滤器设置了所有必需的标头 - 无缓存,无存储,必须重新验证等。但是

  • 这不适用于所有浏览器
  • 尤其是Safari(大多数情况下是移动设备)完全忽略此类设置

我会尝试制作黑客并强制Safari移动刷新,但这不是我所期待的。

我想知道我是否可以:

  • 处理异常而不显示用户存在任何问题(对用户完全透明)
  • 通过替换防伪令牌用户名来防止此问题,这将允许用户再次登录而不会出现此异常,如果与浏览器缓存相关的黑客将在下一版本的浏览器中停止工作。
  • 我真的不想依赖浏览器行为,因为每个行为都不同。

更新1

为了澄清一下,我知道如何处理MVC中的错误。问题是这个处理错误根本解决了我的问题。错误处理的基本思想是重定向到自定义错误页面,并带有好消息。但我希望防止发生此错误,而不是以用户可见的方式处理它。通过句柄我的意思是捕获用户名替换或其他合适的操作然后继续登录。

更新2

我在下面添加了适合我的解决方案。

4 个答案:

答案 0 :(得分:19)

经过一段时间的调查后,我想我找到了一些方法来解决用户的这个错误。它并不完美,但至少没有显示错误页面:

我根据HandleErrorAttribute创建了过滤器:

    [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", 
        Justification = "This attribute is AllowMultiple = true and users might want to override behavior.")]
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class LoginAntiforgeryHandleErrorAttribute : FilterAttribute, IExceptionFilter
    {
        #region Implemented Interfaces

        #region IExceptionFilter

        /// <summary>
        /// </summary>
        /// <param name="filterContext">
        /// The filter context.
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// </exception>
        public virtual void OnException(ExceptionContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }

            if (filterContext.IsChildAction)
            {
                return;
            }

            // If custom errors are disabled, we need to let the normal ASP.NET exception handler
            // execute so that the user can see useful debugging information.
            if (filterContext.ExceptionHandled || !filterContext.HttpContext.IsCustomErrorEnabled)
            {
                return;
            }

            Exception exception = filterContext.Exception;

            // If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
            // ignore it.
            if (new HttpException(null, exception).GetHttpCode() != 500)
            {
                return;
            }

            // check if antiforgery
            if (!(exception is HttpAntiForgeryException))
            {
                return;
            }

            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary
                {
                    { "action", "Index" }, 
                    { "controller", "Home" }
                });

            filterContext.ExceptionHandled = true;
        }

        #endregion

        #endregion
    }

然后我将此过滤器应用于Login POST操作:

[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[LoginAntiforgeryHandleError]
public ActionResult Login(Login model, string returnUrl)
{

此解决方案的主要思想是将防伪例外重定向到主索引操作。如果用户仍未进行未经身份验证,则会显示登录页面,如果用户已经过身份验证,则会显示索引页面。

更新1 此解决方案存在一个潜在问题。如果有人使用不同的凭据登录,那么在出错时应该添加其他登录运行时 - 注销以前的用户并登录新用户。此方案未得到处理。

答案 1 :(得分:18)

如果您只有一个或几个功能受到影响,那么创建过滤器可能会略微技术过度。一个更简单但非通用的解决方案是简单地删除特定方法的[ValidateAntiForgeryToken],并在检查用户是否已登录后添加手动验证。

if (User.Identity.IsAuthenticated)
{
    return RedirectToAction("Index", "Home");
}
System.Web.Helpers.AntiForgery.Validate();
/* proceed with authentication here */

答案 2 :(得分:5)

您应该能够通过添加操作过滤器来处理错误来处理异常。

[HandleError(View="AntiForgeryExceptionView", ExceptionType = typeof(HttpAntiForgeryException))]

Todo请确保在您的web.config中打开自定义错误。

<customErrors mode="On"/>

您还可以查看此blog以获取有关句柄错误的更多信息。

编辑由于您使用的是MVC4,而博客是关于MVC3的,您还可以查看MSDN library - HandleErrorAttribute,但版本不应该有所作为。

答案 3 :(得分:0)

在您之前进行身份验证后,登录时会显示该消息。
重现步骤:
1.) 打开您的登录页面并确认您未通过身份验证。
2.) 复制选项卡并在第二个选项卡上登录。
3.) 返回第一个选项卡并尝试登录(无需重新加载页面)。
4.) 你看到这个错误;如果您的登录操作装饰有 [ValidateAntiForgeryToken] 属性:

<块引用>

System.Web.Mvc.HttpAntiForgeryException:
提供的防伪令牌适用于用户 "",
但当前用户是“YourUserNameOrEmailAddress”。

此 Helper 执行与 [ValidateAntiForgeryToken] 属性相同的验证:

System.Web.Helpers.AntiForgery.Validate()

从登录操作中删除 [ValidateAntiForgeryToken] 并改用此方法。

现在,当用户已经通过身份验证时,它将重定向到主页。
如果已通过身份验证,但以其他人身份登录,则注销当前用户并在以新用户身份进行身份验证之前继续验证防伪令牌。

if (User.Identity.IsAuthenticated)
{
    if (User.Identity.Name == UserName)//User is already Logged in.
        return RedirectToAction("Index", "Home");
    else//Else: User is Logging In as someone else, so Log Out the Current User.
        ResetUser();
}
System.Web.Helpers.AntiForgery.Validate();//Replaces [ValidateAntiForgeryToken].
//Add your Login Logic below here.

然后,添加此功能以安全地重置用户而无需再次重新加载页面:

private void ResetUser()
{
    //Add any additional custom Sign-Out/Log-Off Logic here.
    Session.Abandon();
    FormsAuthentication.SignOut();

    //Source: https://stackoverflow.com/questions/4050925/page-user-identity-isauthenticated-still-true-after-formsauthentication-signout
    //The User.Identity is Read-Only, but it reads from HttpContext.User, which we may Reset.  Otherwise it will still show as Authenticated until the next Page Load.
    HttpContext.User = new System.Security.Principal.GenericPrincipal(new System.Security.Principal.GenericIdentity(string.Empty), null);//Do not set Identity to null, because other parts of the code may assume it's blank.
}

.net核心思想:
我应该注意,如果您使用 .net Core 并且在您的控制器上具有 [AutoValidateAntiforgeryToken] 属性 - 或者您已经向整个站点添加了一个全局过滤器,例如 services.AddMvc(options => { options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); }); - 那么您可以选择使用 [IgnoreAntiforgeryToken] 装饰您的登录操作方法以避免自动验证异常,并让您有机会在继续并手动调用验证帮助程序方法之前重定向或注销。< br/> 注意:我还没有使用 .net Core 来验证这一点,但请在此处添加我的发现,以防万一。