2013-02-20 100 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(); 

要嘗試重現錯誤,步驟如下: 如果你是你的登錄頁面上,你是不是驗證。如果您複製選項卡並使用第二個選項卡登錄。 如果您回到登錄頁面上的第一個標籤,並且您嘗試登錄而不重新加載頁面...您有此錯誤。

相關問題