偽造防止トークンはユーザー「」を対象としていますが、現在のユーザーは「ユーザー名」です


130

1ページのアプリケーションを構築していて、偽造防止トークンに関する問題が発生しています。

問題が発生する理由はわかっていますが、修正方法がわかりません。

次の場合にエラーが発生します。

  1. ログインしていないユーザーがダイアログをロードします(生成された偽造防止トークンを使用)
  2. ユーザーがダイアログを閉じる
  3. ユーザーがログイン
  4. ユーザーが同じダイアログを開く
  5. ユーザーがダイアログでフォームを送信する

偽造防止トークンはユーザー「」を対象としていますが、現在のユーザーは「ユーザー名」です

これが発生する理由は、私のアプリケーションが100%単一ページであり、ユーザーがajax投稿を介してに正常にログインしたときに/Account/JsonLogin、サーバーから返された「認証済みビュー」で現在のビューを切り替えるだけですが、ページ。

手順3と4の間にページを単純に再読み込みするとエラーが発生しないため、これが理由であることはわかっています。

したがって@Html.AntiForgeryToken()、ロードされたフォームでは、ページがリロードされるまで、古いユーザーのトークンが返されるようです。

@Html.AntiForgeryToken()認証された新しいユーザーのトークンを返すように変更するにはどうすればよいですか?

実際に呼び出されるときまでにGenericalPrincipalカスタムIIdentityで新しいを注入しますが、実際には、プロパティがtrueに設定されたカスタムID ですが、ページの再読み込みを行わない限り、古いユーザーのトークンをレンダリングしているようです。Application_AuthenticateRequest@Html.AntiForgeryToken()HttpContext.Current.User.IdentityIsAuthenticated@Html.AntiForgeryToken


@ Html.AntiForgeryTokenコードがリロードなしで呼び出されていることを実際に確認できますか?
カイルC

それは間違いなく、私が言及したようにHttpContext.Current.Userオブジェクトを検査するためにそこでブレークすることに成功できます
議会

2
これを参照してください:stackoverflow.com/a/19471680/193634
Rosdi Kasim

@議会は、あなたが以下の答えであなたがどちらのオプションを選んだか教えていただけますか?
Siddharth Pandey 2014

私が正しく覚えているなら、私は完全なリロードで行くために例外を作ったと思います。しかし、私は新しいプロジェクトですぐにこの問題に遭遇することを期待しています。より適切なオプションを選択すると、ポストバックされます。
議会

回答:


170

これは、偽造防止トークンがユーザーのユーザー名を暗号化されたトークンの一部として埋め込み、検証を強化するために発生します。最初に呼び出すとき@Html.AntiForgeryToken()、ユーザーはログインしていません。そのため、トークンはユーザー名に空の文字列を持ちます。ユーザーがログインした後、偽造防止トークンを置き換えない場合、最初のトークンは匿名ユーザーと、既知のユーザー名を持つ認証されたユーザーがいます。

この問題を解決するには、いくつかのオプションがあります。

  1. 今回だけ、SPAに完全なPOSTを実行させます。ページがリロードされると、更新されたユーザー名が埋め込まれた偽造防止トークンが埋め込まれます。

  2. @Html.AntiForgeryToken()ログイン直後の部分的なビューを表示し、別のAJAXリクエストを実行して、既存の偽造防止トークンをリクエストのレスポンスに置き換えます。

  3. 偽造防止検証が実行するIDチェックを無効にするだけです。以下をApplication_Startメソッドに追加しますAntiForgeryConfig.SuppressIdentityHeuristicChecks = true


21
@議会:あなたはこの答えを受け入れました、あなたが選んだオプションを私たちと共有できますか?
R. Schreurs 2013年

9
素敵でシンプルなオプションの+1。OAuthプロバイダーによる時間指定のログアウトもこの問題を引き起こします。
コーディング

18
オプション3は私にはうまくいきませんでした。ログアウト中に、ログインページに2つのウィンドウを開きました。1つのウィンドウで1人のユーザーとしてログインしてから、別のウィンドウで別のユーザーとしてログインし、同じエラーを受け取りました。
McGaz 2014年

5
残念ながら、これに対する適切な解決策を得ることができませんでした。ログインページからトークンを削除しました。ログイン後も投稿に含めます。
McGaz 2014年

7
オプション3も私にとっては機能しませんでした。引き続き同じエラーが発生します。
Joao Leme 14

25

エラーを修正するには、OutputCacheデータアノテーションをGet ActionResultof Loginページに次のように配置する必要があります。

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

3
これは私にとって問題を解決しました、完全に理にかなっています。ありがとう!
Prime03 2015

私の使用例は、ユーザーがログインを試み、ModelState.AddError()を介して「アカウントが無効になっている」などのエラーが表示された場合です。次に、もう一度ログインをクリックすると、このエラーが表示されます。ただし、この修正により、偽造防止トークンエラーではなく、新しい空白のログインビューが再び表示されました。したがって、修正ではありません。
yourpublicdisplayname 2016

私の場合:1.ユーザーのLogIn()とホームページにアクセスします。2.ユーザーが戻るボタンを押して、ログインビューに戻ります。3.ユーザーが再度ログインして、「偽造防止トークンはユーザー「」を意味しますが、現在のユーザーは「ユーザー名」です」というエラーが表示される。上記のコードを使用しても、ユーザーは戻るボタンを押すことができますが、ホームページにリダイレクトされます。したがって、ユーザーが[戻る]ボタンを何回押しても、ホームページにリダイレクトされます。ありがとう
Ravi

これがXamarin Webビューで機能しない理由はありますか?
Noobie3001


15

これは私のアプリケーションで何度も発生するため、グーグルすることにしました。

このエラーについて簡単な説明が見つかりました!ユーザーはログイン用のボタンをダブルクリックしています!以下のリンクで、別のユーザーがそのことについて話しているのを確認できます。

MVC 4提供の偽造防止トークンはユーザー ""向けでしたが、現在のユーザーは "user"です

お役に立てば幸いです。=)


これが私の問題でした。どうも!
Nanou Ponette、2018年

8

私は同じ問題を抱えていましたが、この汚いハックはそれを修正しました。

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

...


1
同じ問題があったようです。IMOこれはハックではなく、ログイン時に確認する必要がある一般的なことです。ユーザーがすでにログインしている場合は、サインアウトしてログインページを表示します。私の問題を解決しました、ありがとう。
アレクサンドル

7

すでに認証されているときにログインすると、メッセージが表示されます。

このヘルパーは、[ValidateAntiForgeryToken]属性とまったく同じことを行います。

System.Web.Helpers.AntiForgery.Validate()

[ValidateAntiForgeryToken]コントローラーから属性を削除し、このヘルパーをアクションメソッドに配置します。

したがって、ユーザーがすでに認証されている場合は、ホームページにリダイレクトするか、そうでない場合は、この検証後に有効な偽造防止トークンの検証を続行します。

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

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

エラーを再現するには、次の手順を実行します。ログインページが表示されていて、認証されていない場合。タブを複製し、2番目のタブでログインした場合。また、ログインページの最初のタブに戻り、ページを再読み込みせずにログインしようとすると、このエラーが発生します。


優れたソリューション!うまくいかなかった他の多くの提案を試した後、これは私の問題を解決しました。最初は、同じページで2つのブラウザまたはタブが開いていて、ユーザーが1つからログインし、次にリロードせずに2番目からログインしていることが原因であることが判明するまで、エラーを再現するのは面倒でした。
Nicki

この解決策をありがとう。私も働いた。IDがログインユーザー名と同じであるかどうかを確認するチェックを追加しました。そうである場合は、ユーザーのログインを引き続き試み、そうでない場合はログアウトします。たとえば、{System.Web.Helpers.AntiForgery.Validate();} catch(HttpAntiForgeryException){if(!User.Identity.IsAuthenticated || string.Compare(User.Identity.Name、model.Username)!= 0)を試してください。 {//ここでのログオフロジック}}
スティーブオーウェン

2

ほとんどの場合、本番サーバーで同じ例外が発生します。

なぜそれが起こるのですか?

これは、ユーザーが有効な資格情報でログインし、一度ログインして別のページにリダイレクトしたときに発生します。ユーザーが[戻る]ボタンを押すと、ログインページが表示され、有効な資格情報を入力すると、この例外が発生します。

の解き方?

この行を追加して完璧に作業してください。エラーは発生しません。

[OutputCache(NoStore = true, Duration = 0, VaryByParam = "None")]

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

Return View( "AccountDetails")がトークン例外を引き起こしていることがわかりました。これは、ConfirmEmail関数がAllowAnonymousで装飾されていたが、AccountDetails関数にはValidateAntiForgeryTokenがあったためだと思います。

ReturnをReturn RedirectToAction( "AccountDetails")に変更すると、問題が解決しました。


1
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")]

public ActionResult Login(string returnUrl)

これをテストするには、ログイン(取得)アクションの最初の行にブレークポイントを配置します。OutputCacheディレクティブを追加する前は、最初のロードでブレークポイントがヒットしましたが、ブラウザの戻るボタンをクリックした後はヒットしませんでした。ディレクティブを追加した後は、ブレークポイントが毎回ヒットするようになるため、AntiForgeryTokenは空のトークンではなく、正しいものになります。


0

単一ページのASP.NET MVCコアアプリケーションでも同じ問題が発生しました。HttpContext.User現在のIDクレームを変更するすべてのコントローラーアクションを設定することで、これを解決しました(ここで説明するように、MVCは後続の要求に対してのみこれを行うため)。ミドルウェアの代わりに結果フィルターを使用して、偽造防止Cookieを応答に追加しました。これにより、MVCアクションが返された後でのみ生成されることが確認されました。

コントローラー(注:ASP.NET Core Identityでユーザーを管理しています):

[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" });
    }
}

偽造防止Cookieを追加する結果フィルター:

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)
    {
        //...
    }
}

-3

インターネットショップでの偽造防止トークンの検証に問題があります。ユーザーが(商品を使用して)多くのタブを開き、ログイン後に別のタブでログインしようとすると、そのようなAntiForgeryExceptionが発生します。だから、AntiForgeryConfig.SuppressIdentityHeuristicChecks = trueは私には役に立たなかったので、このような醜いハックフィックスを使用しました、おそらく誰かにとって役立つでしょう:

   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());
    }
}

偽造防止トークン生成オプションを設定して、ユーザー名などを除外できると便利だと思います。


12
これは問題の問題に対処する恐ろしい例です。これは使わないでください。
xxbbcc 2014年

xxbbccに完全に同意します。
ハビエル

OK、ユースケース:偽造防止トークンを使用したログインフォーム。2つのブラウザタブで開きます。最初にログインします。あなたはカント 2番目のタブを更新します。2番目のタブからログインしようとするユーザーに正しい動作をさせるには、どのような解決策を提案しますか?
user3364244 2014年

@ user3364244:正しい動作は次のようになります。websocketsまたはsignalRを使用して外部ログインを検出します。これは同じセッションなので、私は推測してそれを機能させることができます:-)
ダンピー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.