Asp.net mvc 4 防伪令牌是给用户的&引用;但当前用户是;用户名";

Asp.net mvc 4 防伪令牌是给用户的&引用;但当前用户是;用户名";,asp.net-mvc-4,membership-provider,Asp.net Mvc 4,Membership Provider,我正在构建一个单页应用程序,但遇到了防伪令牌的问题 我知道问题为什么会发生,只是不知道如何解决 发生以下情况时,我会得到错误: 非登录用户加载对话框(带有生成的防伪令牌) 用户关闭对话框 用户登录 用户打开相同的对话框 用户在对话框中提交表单 防伪令牌用于用户“”,但当前用户为“” “用户名” 发生这种情况的原因是,我的应用程序是100%单页的,当用户通过ajax帖子成功登录到/Account/JsonLogin,我只需使用服务器返回的“已验证视图”切换当前视图,但不重新加载页面。 我知道这就是

我正在构建一个单页应用程序,但遇到了防伪令牌的问题

我知道问题为什么会发生,只是不知道如何解决

发生以下情况时,我会得到错误:

  • 非登录用户加载对话框(带有生成的防伪令牌)
  • 用户关闭对话框
  • 用户登录
  • 用户打开相同的对话框
  • 用户在对话框中提交表单
  • 防伪令牌用于用户“”,但当前用户为“” “用户名”

    发生这种情况的原因是,我的应用程序是100%单页的,当用户通过ajax帖子成功登录到
    /Account/JsonLogin
    ,我只需使用服务器返回的“已验证视图”切换当前视图,但不重新加载页面。

    我知道这就是原因,因为如果我在步骤3和步骤4之间简单地重新加载页面,就不会出现错误

    因此,在重新加载页面之前,加载表单中的
    @Html.AntiForgeryToken()
    似乎仍然会为旧用户返回一个令牌

    如何更改
    @Html.AntiForgeryToken()
    为新的经过身份验证的用户返回令牌?


    我在每个
    应用程序\u AuthenticateRequest
    上注入了一个新的
    GenericalPrincipal
    和一个自定义的
    IIdentity
    ,因此当
    @Html.AntiForgeryToken()
    被调用
    HttpContext.Current.User.Identity
    时,事实上,我的
    自定义标识已通过身份验证
    属性设置为true,但
    @Html。除非我重新加载页面,否则AntiForgeryToken
    似乎仍会为老用户呈现令牌。

    发生这种情况是因为防伪令牌将用户的用户名作为加密令牌的一部分嵌入,以便更好地验证。当您第一次调用
    @Html.AntiForgeryToken()
    时,用户未登录,因此在用户登录后,令牌的用户名将为空字符串,如果不替换防伪令牌,它将不会通过验证,因为初始令牌用于匿名用户,现在我们有一个具有已知用户名的经过身份验证的用户

    您有几个选项可以解决此问题:

  • 就在这一次,让你的SPA做一个完整的帖子,当页面重新加载时,它将有一个嵌入更新用户名的防伪令牌

  • 只需使用
    @Html.AntiForgeryToken()
    进行部分查看,登录后立即执行另一个AJAX请求,并用请求的响应替换现有的防伪令牌


  • 请注意,设置AntiForgeryConfig.suppressIdentityEuristicChecks=true不会禁用用户名验证,它只是更改验证的工作方式。请参见,读取该属性的位置,以及验证令牌中用户名的位置,而不管该配置的值如何。

    在网店中验证防伪令牌存在问题:用户打开许多选项卡(带有商品),登录一个选项卡后,尝试登录另一个选项卡,并获得此类反伪造异常。 因此,AntiForgeryConfig.suppressidentityhyeuristicchecks=true对我没有帮助,所以我使用了如此丑陋的hackfix,也许它会对某人有所帮助:

       public class ExceptionPublisherExceptionFilter : IExceptionFilter
    {
        public void OnException(ExceptionContext exceptionContext)
        {
            var exception = exceptionContext.Exception;
    
            var request = HttpContext.Current.Request;
            if (request != null)
            {
                if (exception is HttpAntiForgeryException &&
                    exception.Message.ToLower().StartsWith("the provided anti-forgery token was meant for user \"\", but the current user is"))
                {
                    var isAjaxCall = string.Equals("XMLHttpRequest", request.Headers["x-requested-with"], StringComparison.OrdinalIgnoreCase);
                    var returnUrl = !string.IsNullOrWhiteSpace(request["returnUrl"]) ? request["returnUrl"] : "/";
                    var response = HttpContext.Current.Response;
    
                    if (isAjaxCall)
                    {
                        response.Clear();
                        response.StatusCode = 200;
                        response.ContentType = "application/json; charset=utf-8";
                        response.Write(JsonConvert.SerializeObject(new { success = 1, returnUrl = returnUrl }));
                        response.End();
                    }
                    else
                    {
                        response.StatusCode = 200;
                        response.Redirect(returnUrl);
                    }
                }
            }
    
    
            ExceptionHandler.HandleException(exception);
        }
    }
    
    public class FilterConfig
    {
        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new ExceptionPublisherExceptionFilter());
            filters.Add(new HandleErrorAttribute());
        }
    }
    

    如果可以设置防伪令牌生成选项,以排除用户名或类似的内容,那就太好了。

    要修复错误,您需要在登录页面的Get
    ActionResult
    上放置
    OutputCache
    数据注释,如下所示:

    [OutputCache(NoStore=true, Duration = 0, VaryByParam= "None")] 
    public ActionResult Login(string returnUrl)
    

    这种情况在我的应用程序中经常发生,所以我决定用谷歌搜索它

    我找到了一个关于这个错误的简单解释! 用户正在双击按钮进行登录!您可以在下面的链接上看到另一个用户正在谈论这一点:


    我希望这会有帮助!=)

    我在注册过程中遇到了一个相当具体但类似的问题。一旦用户单击发送给他们的电子邮件链接,他们就会登录并直接发送到帐户详细信息屏幕以填写更多信息。我的代码是:

        Dim result = Await UserManager.ConfirmEmailAsync(userId, code)
        If result.Succeeded Then
            Dim appUser = Await UserManager.FindByIdAsync(userId)
            If appUser IsNot Nothing Then
                Dim signInStatus = Await SignInManager.PasswordSignInAsync(appUser.Email, password, True, shouldLockout:=False)
                If signInStatus = SignInStatus.Success Then
                    Dim identity = Await UserManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ApplicationCookie)
                    AuthenticationManager.SignIn(New AuthenticationProperties With {.IsPersistent = True}, identity)
                    Return View("AccountDetails")
                End If
            End If
        End If
    
    我发现返回视图(“AccountDetails”)给了我令牌异常,我猜是因为ConfirmMail函数用AllowAnonymous修饰,但AccountDetails函数有ValidateAntiForgeryToken


    将Return-to-Return重定向操作(“AccountDetails”)更改为Return-to-Return-redirectoaction(“AccountDetails”)为我解决了这个问题。

    我也遇到了同样的问题,而这个肮脏的黑客已经修复了它,至少在我能够以更干净的方式修复它之前是如此

        public ActionResult Login(string returnUrl)
        {
            if (AuthenticationManager.User.Identity.IsAuthenticated)
            {
                AuthenticationManager.SignOut();
                return RedirectToAction("Login");
            }
    

    我对单页ASP.NET MVC核心应用程序也有同样的问题。我通过在所有更改当前身份声明的控制器操作中设置
    HttpContext.User
    (因为MVC仅对后续请求执行此操作,如上所述)来解决此问题。我使用一个结果过滤器而不是中间件将反伪造cookie附加到我的响应中,这确保了它们只在MVC操作返回后生成

    控制器(注意,我正在使用ASP.NET核心标识管理用户):

    Startup.cs摘录:

    public partial class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            //...
        }
    
        public IConfigurationRoot Configuration { get; }
    
        public void ConfigureServices(IServiceCollection services)
        {
    
            //...
    
            services.AddAntiforgery(options =>
            {
                options.HeaderName = "MyXSRFFieldTokenHeaderName";
            });
    
    
            services.AddMvc(options =>
            {
                options.Filters.Add(typeof(XSRFCookieFilter));
            });
    
            services.AddScoped<XSRFCookieFilter>();
    
            //...
        }
    
        public void Configure(
            IApplicationBuilder app,
            IHostingEnvironment env,
            ILoggerFactory loggerFactory)
        {
            //...
        }
    }
    
    公共部分类启动
    {
    公共启动(IHostingEnvironment环境)
    {
    //...
    }
    公共IConfigurationRoot配置{get;}
    public void配置服务(IServiceCollection服务)
    {
    //...
    services.AddAntiforgery(选项=>
    {
    options.HeaderName=“MyXSRFFieldTokenHeaderName”;
    });
    services.AddMvc(选项=>
    {
    options.Filters.Add(typeof(XSRFCookieFilter));
    });
    services.addScope();
    //...
    }
    公共无效配置(
    IApplicationBuilder应用程序,
    IHostingEnvironment环境,
    伊洛格工厂(伐木厂)
    {
    //...
    }
    }
    
    您可以通过在登录(Get)操作的第一行上放置断点来测试这一点。在添加OutputCache指令之前,断点应该是
    public class XSRFCookieFilter : IResultFilter
    {
        IAntiforgery antiforgery;
    
        public XSRFCookieFilter(IAntiforgery antiforgery)
        {
            this.antiforgery = antiforgery;
        }
    
        public void OnResultExecuting(ResultExecutingContext context)
        {
            var HttpContext = context.HttpContext;
            AntiforgeryTokenSet tokenSet = antiforgery.GetAndStoreTokens(context.HttpContext);
            HttpContext.Response.Cookies.Append(
                "MyXSRFFieldTokenCookieName",
                tokenSet.RequestToken,
                new CookieOptions() {
                    // Cookie needs to be accessible to Javascript so we
                    // can append it to request headers in the browser
                    HttpOnly = false
                } 
            );
        }
    
        public void OnResultExecuted(ResultExecutedContext context)
        {
    
        }
    }
    
    public partial class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            //...
        }
    
        public IConfigurationRoot Configuration { get; }
    
        public void ConfigureServices(IServiceCollection services)
        {
    
            //...
    
            services.AddAntiforgery(options =>
            {
                options.HeaderName = "MyXSRFFieldTokenHeaderName";
            });
    
    
            services.AddMvc(options =>
            {
                options.Filters.Add(typeof(XSRFCookieFilter));
            });
    
            services.AddScoped<XSRFCookieFilter>();
    
            //...
        }
    
        public void Configure(
            IApplicationBuilder app,
            IHostingEnvironment env,
            ILoggerFactory loggerFactory)
        {
            //...
        }
    }
    
    [OutputCache(NoStore=true, Duration=0, VaryByParam="None")]
    
    public ActionResult Login(string returnUrl)
    
    System.Web.Helpers.AntiForgery.Validate()
    
    if (User.Identity.IsAuthenticated)
    {
        return RedirectToAction("Index", "Home");
    }
    
    System.Web.Helpers.AntiForgery.Validate();
    
    [OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]