38

这是场景:

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

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

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

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

我将尝试进行黑客攻击并强制 safari mobile 刷新,但这不是我所期望的。

我想知道我是否可以:

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

更新 1

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

更新 2

我添加了以下对我有用的解决方案。

4

5 回答 5

21

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

我创建了基于以下内容的过滤器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
    }

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

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

该解决方案的主要思想是将防伪异常重定向到主索引操作。如果用户仍然不会未经身份验证,它将显示登录页面,如果用户已经通过身份验证,它将显示索引页面。

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

于 2013-09-10T10:49:13.697 回答
21

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

if (User.Identity.IsAuthenticated)
{
    return RedirectToAction("Index", "Home");
}
System.Web.Helpers.AntiForgery.Validate();
/* proceed with authentication here */
于 2014-04-09T14:03:26.857 回答
5

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

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

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

<customErrors mode="On"/>

您还可以查看此博客以获取有关处理错误的更多信息。

编辑由于您使用的是 MVC4 并且博客是关于 MVC3 的,您还可以查看MSDN 库 - HandleErrorAttribute,但版本应该没有什么区别。

于 2012-10-22T15:16:15.430 回答
1

一个老问题 - 但我今天遇到了这个问题,我解决它的方法是重定向到注销操作,如下所示:

public ActionResult Login(string returnUrl) 
{
    if (WebSecurity.IsAuthenticated)
        return RedirectToAction("LogOff");

    ...
}
于 2015-08-25T07:12:04.110 回答
0

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

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

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

于 2021-08-06T18:19:28.110 回答