2013-02-20 90 views
105

我正在构建单页应用程序并遇到防伪令牌的问题。反伪造令牌是为用户“”,但当前用户是“用户名”

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

我得到的错误,当出现以下情况:

  1. 非登录用户加载对话框(与所生成的防伪标记)
  2. 用户关闭对话框
  3. 用户登录
  4. 用户打开同一个对话框
  5. 用户提交表单对话框

防伪造令牌是为用户“”但目前的用户 “用户名”

出现这种情况的原因是因为我的应用程序是100%的单页,和当用户成功通过登录一个AJAX帖子/Account/JsonLogin,我只是简单地将当前视图与服务器返回的“已验证视图”切换,但不会重新加载页面。

我知道这是原因,因为如果我简单地重新加载步骤3和4之间的页面,没有错误。

因此,似乎@Html.AntiForgeryToken()在加载的形式仍然返回旧用户令牌,直到页面重新加载。

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

我这样的时候@Html.AntiForgeryToken()被称为HttpContext.Current.User.Identity,其实我与IsAuthenticated属性自定义标识设置为true,但@Html.AntiForgeryToken似乎仍然呈现旧令牌注入新的GenericalPrincipal对每Application_AuthenticateRequest定制IIdentity用户,除非我做一个页面重新加载。

+0

你真的可以验证@ Html.AntiForgeryToken代码是否被调用而不重新加载吗? – 2013-02-20 01:04:55

+0

它肯定是,我可以成功打破那里检查HttpContext.Current.User对象,就像我提到的 – parliament 2013-02-20 05:59:02

+2

请参阅:http://stackoverflow.com/a/19471680/193634 – 2014-01-23 05:27:22

回答

155

发生这种情况的原因是防伪令牌将用户的用户名作为加密令牌的一部分嵌入以进行更好的验证。当您第一次拨打@Html.AntiForgeryToken()时,用户没有登录,因此令牌会有一个用于用户名的空字符串,在用户登录后,如果您没有更换防伪令牌,它将不会通过验证,因为初始令牌为对于匿名用户而言,现在我们拥有一个具有已知用户名的已认证用户。

您有几种选择来解决这个问题:

  1. 就在这个时候让你的SPA做了充分的POST并在页面重新加载它会与嵌入式更新的用户名的防伪标记。

  2. 只有@Html.AntiForgeryToken()的局部视图,并且在登录后,执行另一个AJAX请求并用请求的响应替换现有的防伪标记。

  3. 只需禁用身份检查防伪验证执行。将以下内容添加到您的Application_Start方法:AntiForgeryConfig.SuppressIdentityHeuristicChecks = true

+18

@议会:你接受了这个答案,你可以与我们分享你选择的选项吗? – 2013-05-17 08:56:40

+8

对于nice和simple选项为+1 3. OAuth提供者进行定时注销也会导致此问题。 – 2013-10-09 13:04:26

+3

对于选项3是+1。它很难找出我的网站上发生了这种情况。切换用户或注销导致此错误,但当我刷新它的消失。 EHH。谢谢。这固定了它。 – ppumkin 2014-01-30 14:32:14

-3

已经与互联网商店防伪令牌验证一个问题:用户打开多个标签页(货物)和一个尝试登录后登录另一个和得到了这样AntiForgeryException。 所以,AntiForgeryConfig.SuppressIdentityHeuristicChecks =真没有帮助我,所以我用这么丑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()); 
    } 
} 

认为这将是巨大的,如果防伪令牌生成选项可以设置,以排除用户名或类似的东西。

+10

这是处理问题中一个可怕的例子。不要使用这个。 – xxbbcc 2014-03-04 16:36:05

+0

完全同意xxbbcc。 – Javier 2014-03-04 21:47:16

+0

好的,用例:带有防伪标记的登录表单。在2个浏览器标签中打开它。首先登录。您_cant_刷新第二个选项卡。你建议什么样的解决方案对于试图从第二个标签登录的用户有正确的行为? – user3364244 2014-03-06 13:33:52

14

要解决,你需要把OutputCache数据诠释登录页面获取ActionResult的错误:

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

这解决了我的问题,使总体感。谢谢! – Prime03 2015-08-24 18:33:39

+4

为什么这可以解决问题? – 2016-06-16 06:55:31

+0

我的用例是用户尝试登录,并显示错误,例如“通过ModelState.AddError()禁用帐户”。然后,如果他们再次点击登录,他们会看到这个错误。然而,这个修复只是给了他们一个空白的新的登录视图,而不是反伪造令牌错误。 所以,不是一个修复。 – yourpublicdisplayname 2016-08-29 01:22:05

1

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

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”)是给我的令牌例外,我猜是因为ConfirmEmail功能与使用AllowAnonymous装饰,但AccountDetails功能有ValidateAntiForgeryToken。

更改Return返回RedirectToAction(“AccountDetails”)为我解决了这个问题。

3

我有同样的问题,这个肮脏的黑客得到了修复,至少直到我可以用更干净的方式修复它。

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

...

0

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

控制器(注意:我使用ASP管理用户。NET核心身份):

[Authorize] 
[ValidateAntiForgeryToken] 
public class AccountController : Controller 
{ 
    private SignInManager<IdentityUser> signInManager; 
    private UserManager<IdentityUser> userManager; 
    private IUserClaimsPrincipalFactory<IdentityUser> userClaimsPrincipalFactory; 

    public AccountController(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory) 
    { 
     this.signInManager = signInManager; 
     this.userManager = userManager; 
     this.userClaimsPrincipalFactory = userClaimsPrincipalFactory; 
    } 

    [HttpPost] 
    [AllowAnonymous] 
    public async Task<IActionResult> Login(string username, string password) 
    { 
     if (username == null || password == null) 
     { 
      return BadRequest(); // Alias of 400 response 
     } 

     var result = await signInManager.PasswordSignInAsync(username, password, false, lockoutOnFailure: false); 
     if (result.Succeeded) 
     { 
      var user = await userManager.FindByNameAsync(username); 

      // Must manually set the HttpContext user claims to those of the logged 
      // in user. Otherwise MVC will still include a XSRF token for the "null" 
      // user and token validation will fail. (MVC appends the correct token for 
      // all subsequent reponses but this isn't good enough for a single page 
      // app.) 
      var principal = await userClaimsPrincipalFactory.CreateAsync(user); 
      HttpContext.User = principal; 

      return Json(new { username = user.UserName }); 
     } 
     else 
     { 
      return Unauthorized(); 
     } 
    } 

    [HttpPost] 
    public async Task<IActionResult> Logout() 
    { 
     await signInManager.SignOutAsync(); 

     // Removing identity claims manually from the HttpContext (same reason 
     // as why we add them manually in the "login" action). 
     HttpContext.User = null; 

     return Json(new { result = "success" }); 
    } 
} 

结果过滤追加防伪饼干:

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) 
    { 

    } 
} 

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) 
    { 
     //... 
    } 
} 
0
[OutputCache(NoStore=true, Duration = 0, VaryByParam= “None”)] 

public ActionResult Login(string returnUrl) 

您可以通过将一个破发点测试该在您的登录(Get)操作的第一行。在添加OutputCache指令之前,会在第一次加载时触发断点,但是在单击浏览器后退按钮后它不会。添加完指令后,每次都会触发断点,所以AntiForgeryToken将成为正确的,而不是空的。

0

当您登录时,当您已通过身份验证时,会显示消息。

该助手的功能与[ValidateAntiForgeryToken]属性完全相同。

System.Web.Helpers.AntiForgery.Validate() 

从控制器中删除[ValidateAntiForgeryToken]属性,并将此帮助程序置于操作方法中。

所以当用户已经被认证时,重定向到主页,或者如果没有在验证之后继续验证有效的防伪标记。

if (User.Identity.IsAuthenticated) 
{ 
    return RedirectToAction("Index", "Home"); 
} 

System.Web.Helpers.AntiForgery.Validate(); 

要尝试重现错误,步骤如下: 如果你是你的登录页面上,你是不是验证。如果您复制选项卡并使用第二个选项卡登录。 如果您回到登录页面上的第一个标签,并且您尝试登录而不重新加载页面...您有此错误。