위변조 방지 토큰은 ""사용자를위한 것이지만 현재 사용자는 "username"입니다.


130

단일 페이지 응용 프로그램을 작성하고 위조 방지 토큰에 문제가 있습니다.

문제가 발생하는 이유를 알고 있으며 문제를 해결하는 방법을 모르겠습니다.

다음과 같은 경우 오류가 발생합니다.

  1. 로그인하지 않은 사용자는 대화 상자를 생성합니다 (위조 방지 토큰 생성).
  2. 사용자가 대화 상자를 닫습니다.
  3. 사용자 로그인
  4. 사용자는 같은 대화 상자를 엽니 다
  5. 사용자가 대화 상자에서 양식을 제출

위변조 방지 토큰은 ""사용자를위한 것이지만 현재 사용자는 "username"입니다.

내 응용 프로그램이 100 % 단일 페이지이며, 사용자가 성공적으로 아약스 게시물을 통해 로그인 할 때 때문에 이런 일이 발생하는 이유는 /Account/JsonLogin, 단순히 서버에서 반환 된 "인증 된 뷰"로 현재보기를 전환하지만 를 다시로드하지 않습니다 페이지.

3 단계와 4 단계 사이에 페이지를 간단히 다시로드하면 오류가 없기 때문에 이것이 이유라는 것을 알고 있습니다.

따라서 @Html.AntiForgeryToken()로드 된 양식에서 페이지를 다시로드 할 때까지 여전히 이전 사용자의 토큰을 반환하는 것으로 보입니다 .

@Html.AntiForgeryToken()인증 된 새로운 사용자에 대한 토큰을 반환하도록 변경 하려면 어떻게 해야합니까?

호출 할 때 마다 GenericalPrincipal사용자 정의 IIdentity에 새 항목 을 삽입합니다 . 실제로 속성이 true로 설정된 사용자 정의 ID 는 페이지를 다시로드하지 않으면 여전히 이전 사용자에 대한 토큰을 렌더링하는 것 같습니다.Application_AuthenticateRequest@Html.AntiForgeryToken()HttpContext.Current.User.IdentityIsAuthenticated@Html.AntiForgeryToken


실제로 @ Html.AntiForgeryToken 코드가 다시로드되지 않고 호출되는지 확인할 수 있습니까?
Kyle C

확실히 내가 언급처럼 객체 HttpContext.Current.User를 성공적으로 휴식이 검사 할 수 있습니다
의회

2
이것을 참조하십시오 : stackoverflow.com/a/19471680/193634
Rosdi Kasim

@parliament 아래 답변에서 어떤 옵션을 선택했는지 알려주십시오.
Siddharth Pandey

올바르게 기억하면 전체 다시로드와 함께 예외를 만들었다 고 생각합니다. 그러나 새로운 프로젝트에서 곧이 문제가 발생할 것으로 예상됩니다. 더 나은 작업 옵션을 선택하면 다시 게시됩니다.
국회

답변:


170

위조 방지 토큰은 더 나은 유효성 검사를 위해 사용자의 사용자 이름을 암호화 된 토큰의 일부로 포함하기 때문에 발생합니다. 처음 호출 할 @Html.AntiForgeryToken()때 사용자가 로그인하지 않았으므로 토큰에 사용자 이름에 대한 빈 문자열이 표시됩니다. 사용자가 로그인 한 후 위조 방지 토큰을 바꾸지 않으면 초기 토큰이 사용 되었기 때문에 유효성 검사를 통과하지 못합니다. 익명의 사용자이며 이제 사용자 이름이 알려진 인증 된 사용자가 있습니다.

이 문제를 해결하기위한 몇 가지 옵션이 있습니다.

  1. 이번에는 SPA가 전체 POST를 수행하도록하고 페이지가 다시로드 될 때 업데이트 된 사용자 이름이 포함 된 위조 방지 토큰을 갖게됩니다.

  2. @Html.AntiForgeryToken()로그인 한 직후에 부분적으로 확인하고 다른 AJAX 요청을 수행하고 기존 위조 방지 토큰을 요청의 응답으로 바꿉니다.

  3. 위조 방지 유효성 검사가 수행하는 신원 확인을 비활성화하십시오. Application_Start 메소드에 다음을 추가하십시오 AntiForgeryConfig.SuppressIdentityHeuristicChecks = true..


21
@parliament :이 답변을 수락했습니다. 선택한 옵션을 공유 할 수 있습니까?
R. Schreurs

9
훌륭하고 간단한 옵션 인 경우 +1. OAuth 공급자가 시간 초과 로그 아웃하면이 문제가 발생합니다.
코딩 사라지다

18
옵션 3이 작동하지 않았습니다. 로그 아웃하는 동안 로그인 페이지에 두 개의 창을 열었습니다. 한 창에 한 사용자로 로그인 한 후 다른 창에 다른 사용자로 로그인하여 동일한 오류가 발생했습니다.
McGaz

5
불행히도, 나는 이것에 대한 좋은 해결책을 얻지 못했습니다. 로그인 페이지에서 토큰을 제거했습니다. 로그인 한 후에도 게시물에 포함시킵니다.
McGaz

7
옵션 3은 나에게도 효과가 없었습니다. 여전히 같은 오류가 발생합니다.
Joao Leme

25

오류를 해결하려면 다음과 같이 로그인 정보 페이지 OutputCache에 데이터 주석 을 배치해야합니다 ActionResult.

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

3
이것은 나를 위해 문제를 해결했습니다. 감사!
Prime03

내 유스 케이스는 사용자가 로그인을 시도했으며 ModelState.AddError ()를 통해 "account disabled"와 같은 오류가 표시되었습니다. 그런 다음 로그인을 다시 클릭하면이 오류가 표시됩니다. 그러나이 수정은 위조 방지 토큰 오류가 아닌 빈 로그인 로그인을 다시 표시했습니다. 따라서 수정이 아닙니다.
yourpublicdisplayname

내 경우 : 1. 사용자 로그인 () 및 홈페이지에 도착. 2. 사용자가 뒤로 버튼을 누르고 로그인보기로 돌아갑니다. 3. 사용자가 다시 로그인하고 "위조 방지 토큰은" "사용자를위한 것이지만 현재 사용자는"username ""이라는 오류가 표시됩니다. 오류 페이지에서 사용자가 메뉴에서 다른 탭을 클릭하면 응용 프로그램이 예상대로 작동하고 있습니다 . 위의 코드를 사용하면 여전히 뒤로 버튼을 누를 수 있지만 홈 페이지로 리디렉션됩니다. 따라서 사용자가 뒤로 버튼을 몇 번 누르더라도 홈 페이지로 리디렉션됩니다. 감사합니다
Ravi

이것이 Xamarin 웹보기에서 작동하지 않는 이유가 있습니까?
Noobie3001


15

내 응용 프로그램에서 여러 번 발생하므로 Google에서 사용하기로 결정했습니다!

이 오류에 대한 간단한 설명을 찾았습니다! 사용자가 로그인 버튼을 두 번 클릭하고 있습니다! 다른 사용자가 아래 링크에서 그것에 대해 이야기하는 것을 볼 수 있습니다.

위조 방지 토큰을 제공 한 MVC 4는 사용자 ""을위한 것이지만 현재 사용자는 "user"입니다.

도움이 되길 바랍니다! =)


이것은 내 문제였다. 고마워!
Nanou Ponette

8

나는 같은 문제가 있었고,이 더러운 해킹은 적어도 더 깨끗한 방식으로 고칠 수있을 때까지 해결되었습니다.

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

...


1
같은 문제가있는 것 같습니다. IMO는 해킹이 아니며, 로그인 할 때 모두 확인해야하는 것이 일반적입니다. 사용자가 이미 로그인 한 경우 로그 아웃하고 로그인 페이지를 표시하십시오. 내 문제를 해결했습니다. 감사합니다.
Alexandre

7

이미 인증 된 상태에서 로그인하면 메시지가 나타납니다.

이 도우미는 [ValidateAntiForgeryToken]속성 과 정확히 동일한 기능을 수행 합니다.

System.Web.Helpers.AntiForgery.Validate()

[ValidateAntiForgeryToken]컨트롤러 에서 속성을 제거 하고이 헬퍼를 조치 방법에 배치하십시오.

따라서 사용자가 이미 인증 된 경우 홈 페이지로 리디렉션하거나이 확인 후 유효한 위조 방지 토큰 확인을 계속하지 않으면

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

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

오류를 재현하려면 다음과 같이 진행하십시오. 로그인 페이지에 있고 인증되지 않은 경우. 탭을 복제하고 두 번째 탭으로 로그인 한 경우 그리고 로그인 페이지의 첫 번째 탭으로 돌아와서 페이지를 다시로드하지 않고 로그인하려고하면 ...이 오류가 발생합니다.


훌륭한 솔루션! 이것은 작동하지 않는 다른 많은 제안을 시도한 후에 내 문제를 해결했습니다. 먼저 동일한 페이지에서 두 개의 브라우저 또는 탭이 열리고 사용자가 한 페이지에서 로그인 한 다음 다시로드하지 않고 두 번째 브라우저에서 로그인 할 수 있음을 알기 전까지는 오류를 재현하는 것이 어려웠습니다.
Nicki

이 솔루션에 감사드립니다. 나도 일했다. Identity가 로그인 사용자 이름과 같은지 확인하기 위해 확인을 추가했으며, 그렇다면 사용자를 계속 로그인하고 로그인하지 않으면 계속 로그 아웃합니다. 예를 들어 {System.Web.Helpers.AntiForgery.Validate ();} catch (HttpAntiForgeryException) {if (! User.Identity.IsAuthenticated || string.Compare (User.Identity.Name, model.Username)! = 0) {// 여기서 로그 오프 논리}}
Steve Owen

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 to Return RedirectToAction ( "AccountDetails")을 변경하면 문제가 해결되었습니다.


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

public ActionResult Login(string returnUrl)

로그인 (Get) 조치의 첫 번째 행에 중단 점을 두어이를 테스트 할 수 있습니다. OutputCache 지시문을 추가하기 전에 첫 번째로드에서 중단 점이 발생하지만 브라우저 뒤로 버튼을 클릭하면 중단되지 않습니다. 지시문을 추가 한 후에는 매번 중단 점이 발생하므로 AntiForgeryToken은 비어있는 것이 아니라 핵심 토큰이됩니다.


0

단일 페이지 ASP.NET MVC Core 응용 프로그램에서 동일한 문제가 발생했습니다. HttpContext.User현재 ID 클레임을 변경하는 모든 컨트롤러 작업을 설정 하여 문제를 해결했습니다 (MVC는 여기 에서 논의 된 것처럼 후속 요청에 대해서만 수행하므로 ). 미들웨어 대신 결과 필터를 사용하여 위조 방지 쿠키를 응답에 추가하여 MVC 작업이 반환 된 후에 만 ​​쿠키가 생성되도록했습니다.

컨트롤러 (NB. 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" });
    }
}

위조 방지 쿠키를 추가하기위한 결과 필터 :

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는 나에게 도움이되지 않았으므로 그런 추한 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());
    }
}

위조 방지 토큰 생성 옵션을 설정하여 사용자 이름이나 이와 유사한 것을 제외하면 좋을 것이라고 생각하십시오.


12
이것은 문제의 문제를 다루는 끔찍한 예입니다. 이것을 사용하지 마십시오.
xxbbcc

xxbbcc에 전적으로 동의합니다.
Javier

확인, 유스 케이스 : 위조 방지 토큰이있는 로그인 양식. 2 개의 브라우저 탭에서 엽니 다. 먼저 로그인하십시오. 당신은 어차피 두 번째 탭을 새로 고칩니다. 두 번째 탭에서 로그인을 시도하는 사용자에게 올바른 동작을 제안하는 솔루션은 무엇입니까?
user3364244

@ user3364244 : 올바른 동작은 다음과 같습니다. 웹 소켓 또는 signalR을 사용하여 외부 로그인을 감지합니다. 이것은 같은 세션이므로 작동하도록 만들 수 있습니다. :-)
dampee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.