Mã thông báo chống giả mạo có nghĩa là cho người dùng, nhưng người dùng hiện tại là tên người dùng


130

Tôi đang xây dựng một ứng dụng trang duy nhất và gặp sự cố với mã thông báo chống giả mạo.

Tôi biết tại sao sự cố xảy ra Tôi chỉ không biết cách khắc phục.

Tôi nhận được lỗi khi điều sau đây xảy ra:

  1. Người dùng không đăng nhập tải một hộp thoại (với mã thông báo chống giả mạo được tạo)
  2. Người dùng đóng hộp thoại
  3. Người dùng đăng nhập
  4. Người dùng mở hộp thoại tương tự
  5. Người dùng gửi biểu mẫu trong hộp thoại

Mã thông báo chống giả mạo có nghĩa là cho người dùng "" nhưng người dùng hiện tại là "tên người dùng"

Lý do điều này xảy ra là vì ứng dụng của tôi là một trang 100% và khi người dùng đăng nhập thành công qua bài đăng ajax /Account/JsonLogin, tôi chỉ cần chuyển chế độ xem hiện tại với "chế độ xem được xác thực" được trả về từ máy chủ nhưng không tải lại trang.

Tôi biết đây là lý do vì nếu tôi đơn giản tải lại trang giữa các bước 3 và 4, không có lỗi.

Vì vậy, có vẻ như @Html.AntiForgeryToken()trong biểu mẫu được tải vẫn trả về mã thông báo cho người dùng cũ cho đến khi trang được tải lại.

Làm cách nào tôi có thể thay đổi @Html.AntiForgeryToken()để trả lại mã thông báo cho người dùng mới, được xác thực?

Tôi tiêm một cái mới GenericalPrincipalvới một tùy chỉnh IIdentityvào mỗi Application_AuthenticateRequestlần @Html.AntiForgeryToken()được gọi HttpContext.Current.User.Identitylà, trên thực tế, Nhận dạng tùy chỉnh của tôi vớiIsAuthenticated tính được đặt thành đúng và @Html.AntiForgeryTokendường như vẫn hiển thị mã thông báo cho người dùng cũ trừ khi tôi tải lại trang.


Bạn thực sự có thể xác minh rằng mã @ Html.AntiForgeryToken đang được gọi mà không cần tải lại không?
Kyle C

Đó chắc chắn là, tôi có thể phá vỡ thành công ở đó để kiểm tra HTTPContext. Hiện tại. Đối tượng người dùng như tôi đã đề cập
quốc hội

2
Vui lòng tham khảo điều này: stackoverflow.com/a/19471680/193634
Rosdi Kasim

@parferences bạn có thể vui lòng cho biết bạn đã chọn tùy chọn nào trong câu trả lời dưới đây.
Siddharth Pandey

Tôi tin rằng tôi đã tạo một ngoại lệ để tải lại đầy đủ nếu tôi nhớ chính xác. Nhưng tôi hy vọng sẽ gặp vấn đề này rất sớm trong một dự án mới. Sẽ gửi lại nếu tôi chọn với một tùy chọn làm việc tốt hơn.
quốc hội

Câu trả lời:


170

Điều này xảy ra vì mã thông báo chống giả mạo nhúng tên người dùng của người dùng như một phần của mã thông báo được mã hóa để xác thực tốt hơn. Khi bạn gọi lần đầu tiên @Html.AntiForgeryToken(), người dùng chưa đăng nhập nên mã thông báo sẽ có một chuỗi trống cho tên người dùng, sau khi người dùng đăng nhập, nếu bạn không thay thế mã thông báo chống giả mạo thì nó sẽ không vượt qua xác thực vì mã thông báo ban đầu là dành cho người dùng ẩn danh và bây giờ chúng tôi có một người dùng được xác thực với tên người dùng đã biết.

Bạn có một vài lựa chọn để giải quyết vấn đề này:

  1. Chỉ trong thời gian này, hãy để SPA của bạn thực hiện POST đầy đủ và khi trang tải lại, nó sẽ có mã thông báo chống giả mạo với tên người dùng được cập nhật được nhúng.

  2. Có chế độ xem một phần với chỉ @Html.AntiForgeryToken()và ngay sau khi đăng nhập, thực hiện một yêu cầu AJAX khác và thay thế mã thông báo chống giả mạo hiện tại của bạn bằng phản hồi của yêu cầu.

  3. Chỉ cần vô hiệu hóa kiểm tra danh tính thực hiện xác nhận chống giả mạo. Thêm phần sau vào phương thức Application_Start của bạn : AntiForgeryConfig.SuppressIdentityHeuristicChecks = true.


21
@parferences: bạn đã chấp nhận câu trả lời này, bạn có thể chia sẻ với chúng tôi lựa chọn nào không?
R. Schreurs

9
+1 cho tùy chọn đẹp & đơn giản 3. Đăng xuất đúng lúc bởi các nhà cung cấp OAuth cũng gây ra vấn đề này.
Mã hóa

18
Lựa chọn 3 không phù hợp với tôi. Trong khi đăng xuất, tôi đã mở hai cửa sổ trên trang đăng nhập. Đăng nhập với tư cách một người dùng trong một cửa sổ, sau đó đăng nhập với tư cách người dùng khác trong một cửa sổ khác và nhận được cùng một lỗi.
McGaz

5
Thật không may, tôi không thể có được một giải pháp tốt cho việc này. Tôi đã xóa mã thông báo khỏi trang đăng nhập. Tôi vẫn đưa nó vào bài viết sau khi đăng nhập.
McGaz

7
Lựa chọn 3 cũng không hiệu quả với tôi. Vẫn nhận được lỗi tương tự.
Joao Leme

25

Để khắc phục lỗi, bạn cần đặt OutputCacheChú thích dữ liệu trên trang Nhận ActionResultđăng nhập là:

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

3
Điều này giải quyết vấn đề cho tôi, có ý nghĩa hoàn toàn. Cảm ơn!
Prime03

Trường hợp sử dụng của tôi là người dùng đã thử đăng nhập và nhận thấy lỗi, ví dụ: "tài khoản bị vô hiệu hóa" thông qua ModelState.AddError (). Sau đó, nếu họ nhấp vào đăng nhập một lần nữa, họ sẽ thấy lỗi này. Tuy nhiên, sửa lỗi này chỉ cung cấp cho họ chế độ xem đăng nhập mới trống thay vì lỗi mã thông báo chống giả mạo. Vì vậy, không phải là một sửa chữa.
yourpublicdisplayname

Trường hợp của tôi: 1. Người dùng Đăng nhập () và hạ cánh trên trang chủ. 2. Người dùng nhấn nút quay lại và quay lại giao diện Đăng nhập. 3. Người dùng đăng nhập lại và thấy lỗi "Mã thông báo chống giả mạo có nghĩa là cho người dùng, nhưng người dùng hiện tại là tên người dùng". Trên trang lỗi Nếu người dùng nhấp vào bất kỳ tab nào khác từ menu, ứng dụng đã hoạt động như mong đợi . Sử dụng mã ở trên, người dùng vẫn có thể nhấn nút quay lại nhưng nó được chuyển hướng đến trang chủ. Vì vậy, bất kể bao nhiêu lần người dùng nhấn nút quay lại, nó sẽ chuyển hướng nó đến trang chủ. Cảm ơn bạn
Ravi

Bất cứ ý tưởng tại sao điều này không hoạt động trên một webview Xamarin?
Noobie3001

1
Để được giải thích đầy đủ, hãy xem cải thiện hiệu suất với bộ nhớ đệm đầu ra
stomy

15

Nó xảy ra rất nhiều lần với ứng dụng của tôi, vì vậy tôi quyết định google cho nó!

Tôi tìm thấy một lời giải thích đơn giản về lỗi này! Người dùng đang nhấp đúp vào nút để đăng nhập! Bạn có thể thấy một người dùng khác nói về điều đó trên liên kết dưới đây:

MVC 4 được cung cấp mã thông báo chống giả mạo dành cho người dùng "" nhưng người dùng hiện tại là "người dùng"

Tôi hy vọng nó sẽ giúp! =)


Đây là vấn đề của tôi. Cám ơn!
Nanou Ponette

8

Tôi đã có cùng một vấn đề, và bản hack bẩn này đã sửa nó, ít nhất là cho đến khi tôi có thể sửa nó theo cách sạch hơn.

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

...


1
Có vẻ như tôi đã có cùng một vấn đề. IMO không phải là hack, đó là một điều phổ biến mà tất cả chúng ta nên kiểm tra khi đăng nhập. Nếu người dùng đã đăng nhập, chỉ cần đăng xuất anh ta và hiển thị trang đăng nhập. Đã sửa lỗi của tôi, cảm ơn bạn.
Alexandre

7

Thông báo xuất hiện khi bạn đăng nhập khi bạn đã được xác thực.

Người trợ giúp này thực hiện chính xác điều tương tự như [ValidateAntiForgeryToken]thuộc tính.

System.Web.Helpers.AntiForgery.Validate()

Loại bỏ các [ValidateAntiForgeryToken]thuộc tính từ bộ điều khiển và đặt trình trợ giúp này trong methode hành động.

Vì vậy, khi người dùng đã được xác thực, hãy chuyển hướng đến trang chủ hoặc nếu không tiếp tục với việc xác minh mã thông báo chống giả hợp lệ sau khi xác minh này.

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

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

Để thử tái tạo lỗi, hãy tiến hành như sau: Nếu bạn đang ở trang đăng nhập và bạn không được xác thực. Nếu bạn nhân đôi tab và bạn đăng nhập bằng tab thứ hai. Và nếu bạn quay lại tab đầu tiên trên trang đăng nhập và bạn cố gắng đăng nhập mà không tải lại trang ... bạn đã gặp lỗi này.


Giải pháp tuyệt vời! Điều này đã giải quyết vấn đề của tôi sau khi thử rất nhiều đề xuất khác không hiệu quả. Trước hết, đó là một lỗi đau khi tái tạo lỗi, cho đến khi tôi phát hiện ra rằng đó có thể là do 2 trình duyệt hoặc tab mở cùng một trang và người dùng đăng nhập từ một, sau đó đăng nhập từ trang thứ hai mà không cần tải lại.
Nicki

Cảm ơn giải pháp này. Làm việc cho tôi quá. Tôi đã thêm một kiểm tra để xem liệu Danh tính có giống với tên người dùng đăng nhập hay không và nếu vậy tôi vui vẻ tiếp tục cố gắng đăng nhập người dùng và đăng xuất chúng nếu không. Ví dụ: hãy thử {System.Web.Helpers.AntiForgery.Validate ();} Catch (HttpAntiForgeryException) {if (! User.Identity.IsAuthenticated || string.Compare (User.Identity.Name, model.Username)! {// Logic đăng xuất của bạn ở đây}}
Steve Owen

2

Tôi có cùng một ngoại lệ xảy ra hầu hết thời gian trên máy chủ sản xuất.

Tại sao nó xảy ra?

Nó xảy ra khi người dùng đăng nhập với thông tin xác thực hợp lệ và sau khi đăng nhập và chuyển hướng đến một trang khác, và sau khi họ nhấn nút quay lại sẽ hiển thị trang đăng nhập và một lần nữa anh ta nhập thông tin xác thực hợp lệ vào thời điểm ngoại lệ này.

Giải quyết thế nào?

Chỉ cần thêm dòng này và hoạt động hoàn hảo, không có lỗi.

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

1

Tôi đã có một vấn đề khá cụ thể nhưng tương tự trong quá trình đăng ký. Khi người dùng nhấp vào liên kết email được gửi cho họ, họ sẽ đăng nhập và gửi thẳng đến màn hình chi tiết tài khoản để điền thêm một số thông tin. Mã của tôi là:

    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

Tôi thấy rằng Chế độ xem trả về ("AccountDetails") đã cho tôi ngoại lệ mã thông báo, tôi đoán vì chức năng ConfirmEmail được trang trí với AllowAnonymous nhưng chức năng AccountDetails có ValidateAntiForgeryToken.

Thay đổi Return to Return RedirectToAction ("AccountDetails") đã giải quyết vấn đề cho tôi.


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

public ActionResult Login(string returnUrl)

Bạn có thể kiểm tra điều này bằng cách đặt điểm dừng trên dòng đầu tiên của hành động Đăng nhập (Nhận). Trước khi thêm chỉ thị OutputCache, điểm dừng sẽ được nhấn vào lần tải đầu tiên, nhưng sau khi nhấp vào nút quay lại của trình duyệt thì không được. Sau khi thêm chỉ thị, bạn sẽ kết thúc với điểm dừng bị tấn công mỗi lần, vì vậy AntiForgeryToken sẽ là xác chết, không phải là điểm trống.


0

Tôi gặp vấn đề tương tự với ứng dụng ASP.NET MVC Core một trang. Tôi đã giải quyết nó bằng cách thiết lập HttpContext.Usertrong tất cả các hành động của bộ điều khiển thay đổi xác nhận quyền sở hữu hiện tại (vì MVC chỉ thực hiện điều này cho các yêu cầu tiếp theo, như được thảo luận ở đây ). Tôi đã sử dụng bộ lọc kết quả thay vì phần mềm trung gian để nối các cookie chống phản hồi vào các phản hồi của mình, điều này đảm bảo rằng chúng chỉ được tạo sau khi hành động MVC được trả về.

Trình điều khiển (NB. Tôi đang quản lý người dùng với 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" });
    }
}

Bộ lọc kết quả để chắp thêm cookie chống trộm:

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

    }
}

Trích xuất 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

Có vấn đề với xác thực chống giả mạo trong cửa hàng internet: người dùng mở nhiều tab (có hàng hóa) và sau khi đăng nhập, hãy thử đăng nhập vào một cái khác và nhận AntiForgeryException như vậy. Vì vậy, AntiForgeryConfig.SuppressIdentityHeuristicChecks = true không giúp ích gì cho tôi, vì vậy tôi đã sử dụng hackfix xấu xí như vậy, có thể nó sẽ hữu ích cho ai đó:

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

Hãy nghĩ rằng sẽ rất tuyệt nếu các tùy chọn tạo mã chống giả mạo có thể được đặt, để loại trừ tên người dùng hoặc đại loại như thế.


12
Đây là một ví dụ khủng khiếp về việc xử lý vấn đề trong câu hỏi. Đừng dùng cái này.
xxbbcc

Hoàn toàn đồng ý với xxbbcc.
Javier

OK, trường hợp sử dụng: hình thức đăng nhập với mã thông báo chống giả mạo. Mở nó trong 2 tab trình duyệt. Đăng nhập trước. Bạn không thể làm mới tab thứ hai. Giải pháp nào bạn đề xuất để có hành vi đúng cho người dùng cố gắng đăng nhập từ tab thứ hai?
dùng3364244

@ user3364244: hành vi đúng có thể như sau: phát hiện đăng nhập bên ngoài bằng cách sử dụng websockets hoặc signalR. Đây là cùng một phiên để bạn có thể làm cho nó hoạt động tôi đoán :-)
dampee
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.