web-dev-qa-db-ja.com

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

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

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

次が発生するとエラーが発生します。

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

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

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

これが理由であることがわかります。ステップ3と4の間でページを単純にリロードすればエラーは発生しないからです。

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

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

すべてのApplication_AuthenticateRequestにカスタムGenericalPrincipalを付けて新しいIIdentityを挿入するので、@Html.AntiForgeryToken()が呼び出されるまでにHttpContext.Current.User.Identityが実際にIsAuthenticatedプロパティがtrueに設定されているにもかかわらず、@Html.AntiForgeryTokenは、ページのリロードを行わない限り、古いユーザーのトークンをまだレンダリングしているようです。

119
parliament

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

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

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

  2. @Html.AntiForgeryToken()だけの部分ビューを用意し、ログイン直後に別のAJAXリクエストを実行し、既存の偽造防止トークンをリクエストの応答に置き換えます。

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

164
epignosisx

このエラーを修正するには、ログインページのOutputCacheの取得にActionResultデータ注釈を配置する必要があります。

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

私のアプリケーションでは何度も起こりますので、グーグルで検索することにしました!

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

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

私はそれが役立つことを願っています! =)

10
Ricardo França

私は同じ問題を抱えていましたが、少なくとも汚い方法で修正できるまでは、この汚いハックで修正されました。

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

...

6
mnemonics

実稼働サーバーでほとんどの場合に同じ例外が発生します。

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

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

解決方法?

この行を追加するだけで完璧に機能し、エラーは発生しません。

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

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

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

System.Web.Helpers.AntiForgery.Validate()

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

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

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

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

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

1
A. Morel

登録プロセスの中で、かなり具体的でありながら同様の問題がありました。ユーザーが送信された電子メールのリンクをクリックすると、ユーザーはログインしてアカウントの詳細画面に直接送信され、さらに情報が入力されます。私のコードは:

    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
Liam
[OutputCache(NoStore=true, Duration=0, VaryByParam="None")]

public ActionResult Login(string returnUrl)

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

1
Marian Dalalau

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

コントローラー(注意。ASP.NETCore 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)
    {
        //...
    }
}
0
Ned Howley