2011-10-17 37 views
8

Cơ chế của ASP.NET MVC AntiForgeryToken dựa trên HttpContext.User hiện tại. Nó sử dụng giá trị đó để tạo mã thông báo khi bạn gọi Html.AntiForgeryToken(). Về cơ bản nó là OK (xem giải thích trong last paragraph here) nhưng một vấn đề phát sinh khi bạn đăng nhập thông qua một cuộc gọi Ajax . Trong mã của tôi, khi người dùng đăng nhập, thông tin đăng nhập được gửi dưới dạng đối tượng Json trong Ajax (giá trị trường ẩn AntiForgeryToken ẩn cũng được gửi bên trong Json), máy chủ xác thực người dùng, áp dụng FormsAuthentication.SetAuthCookie() và trả về kết quả Json chứa một số dữ liệu người dùng cụ thể. Bằng cách đó, tôi có thể tránh làm mới toàn bộ trang khi đăng nhập.MVC3 AntiForgeryTắt vỡ khi đăng nhập Ajax

Vấn đề là mọi yêu cầu Ajax tiếp theo đối với máy chủ hiện không thành công theo số ValidateAntiForgeryTokenAttribute, vì bây giờ nó mong đợi một mã thông báo chống giả mạo không tương thích với cookie chống giả mạo.

Làm cách nào để nhận mã thông báo chống giả mạo hợp lệ để đặt vào trường ẩn của khách hàng để mọi yêu cầu Json sau khi đăng nhập thành công?

Tôi cố gắng lấy mã thông báo trường ẩn mới theo cách thủ công (sử dụng AntiForgery.GetHtml() trên hành động, giải nén chuỗi mã thông báo, trả lại cho khách hàng trong Json và đặt nó vào trường ẩn chống giả theo cách thủ công trong JavaScript) nhưng nó không hoạt động - một cuộc gọi Ajax tiếp theo thất bại trên ValidateAntiForgeryTokenAttribute trên máy chủ. Thực tế, mọi cuộc gọi đến AntiForgery.GetHtml() (mà về cơ bản là những gì mà người trợ giúp Html.AntiForgeryToken() làm) tạo ra một mã thông báo khác, làm mất hiệu lực mã thông báo trước đó.

Tôi cũng đã cố gắng đặt HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null); là chi tiết here nhưng không hoạt động.

Lưu ý:This solution không làm việc cho tôi, vì tình hình cụ thể của tôi: Một Ajax đăng nhập làm thay đổi nhận dạng người dùng trên máy chủ và vì thế mỗi thẻ được tạo ra trước khi đăng nhập không hợp lệ; this solution cũng không áp dụng bởi vì nó giải quyết một vấn đề khác.

+1

Tại sao bạn sử dụng AntiForgeryToken trên trang đăng nhập của mình khi người dùng chưa được xác thực. Bạn đang bảo vệ cái gì? –

+2

Tính năng đăng nhập không phải là một trang, nó là một mảnh bên trong mẫu của trang. Nó thực sự không cần thiết trong tính năng đăng nhập, nhưng vấn đề phát sinh sau đó - sau khi phương thức đăng nhập ở phía máy chủ đặt người dùng hiện tại (HttpContext.User) và trả về. Ở giai đoạn này, trang đã có một số trường mã thông báo chống giả mạo ẩn, để phục vụ các cuộc gọi Ajax tiếp theo. –

+0

Phil Haack đăng bài viết này cách đây vài ngày. Đây có phải là cách liên quan đến vấn đề của bạn không? http://haacked.com/archive/2011/10/10/preventing-csrf-with-ajax.aspx ... "Vấn đề nằm ở thực tế là dưới mui xe, sâu bên trong ngăn xếp cuộc gọi, thuộc tính nhìn trộm vào bộ sưu tập Request.Form để lấy mã thông báo chống giả mạo. Nhưng khi bạn đăng dữ liệu được mã hóa JSON, không có bộ sưu tập biểu mẫu nào để nói đến. " – JasperLamarCrabb

Trả lời

6

Bạn sẽ cần phải xóa và làm lại bất kỳ mã thông báo biểu mẫu hiện có nào bạn có khi đăng nhập. Điều này có nghĩa là mã đăng nhập của bạn sẽ phải làm mới trang hiện tại (kinda giết phần ajax của nó eh), thực hiện mã thông báo của riêng bạn hoặc bạn sẽ cần phải làm mới mã thông báo của mình. Có thể yêu cầu xem một phần, trích xuất mã thông báo và cập nhật biểu mẫu của bạn. Bạn thực sự có thể có một url ổn định mà không trả về gì ngoài một mã thông báo cho người dùng đã được xác thực. Người ta có thể tranh luận đây là một vấn đề an ninh, nhưng tôi không tin như vậy bởi vì nó chỉ đơn giản là một cách dễ dàng hơn để có được một mã thông báo thay vì yêu cầu bất kỳ xem-bên hay cách khác.

Bạn sẽ có thể dễ dàng nhận được các trường hợp thẻ để thay thế qua:

 
var token = $('input[name=""__RequestVerificationToken""]'); 

EDIT Sau khi đọc lại thêm vài lần nữa - Tôi hỏi

Tại sao bạn sẽ có một mã thông báo trên hình thức nếu người dùng không đăng nhập. Bạn cho phép cùng một biểu mẫu được 'vận hành' trong khi không đăng nhập và đăng nhập? Hầu hết các trang web trên mạng ngay cả trong trường hợp này sẽ chuyển hướng cho một đăng nhập. Tôi có hiểu chính xác điều này không? Nếu có, bạn có thể muốn xem xét bỏ qua mã thông báo tại đây hoặc sử dụng loại mã thông báo thứ hai cho người dùng chưa được xác thực.Bạn tôi tin rằng đang nói một người dùng chưa được xác thực có thể đã gửi một thứ gì đó trong ứng dụng - một lần nữa nếu tôi hiểu điều này một cách chính xác - mà không được xác thực.

+0

Đối với câu hỏi cuối cùng của bạn, tại sao tôi cần mã thông báo cho người dùng chưa được xác thực, vui lòng xem câu trả lời của tôi cho nhận xét của Ben. Tôi sẽ cố tạo chế độ xem một phần bằng mã thông báo và cập nhật cho bạn kết quả. Nghe có vẻ như một ý tưởng tốt! –

+0

Bạn chưa hiểu, nhưng tôi sẽ chấp nhận câu trả lời của bạn vì nó có vẻ hứa hẹn :-) –

3

Ok, những gì tôi đã làm là kết hợp câu trả lời từ đây: jQuery Ajax calls and the Html.AntiForgeryToken() với một phần. Tôi đang sử dụng loại trực tiếp nhưng đối với những người bạn không quen thuộc với nó, bạn vẫn có thể theo dõi khá dễ dàng.

Đầu tiên html của tôi:

<form id="__AjaxAntiForgeryForm" action="#" method="post">@{Html.RenderPartial("AntiForgeryToken");}</form> 
<div id="loginTestView"> 
    <button data-bind="visible: signedIn() == false,click: signIn">Sign In</button> 
    <button data-bind="visible: signedIn, click: signOut">Sign Out</button> 

    <form> 
     <button data-bind="click: testToken">Test Token</button> 
    </form> 
</div> 

Sự khác biệt chính là thay vì @ Html.AntiForgeryToken() Tôi có một AntiForgeryToken phần có chứa @ Html.AntiForgeryToken().

Vì vậy, để thực sự làm rõ bây giờ tôi có một tập tin AntiForgeryToken.cshtml chỉ:

@Html.AntiForgeryToken() 

Bây giờ khi bạn đăng nhập vào/ra bạn cần phải cập nhật các dấu hiệu như vậy javascript/jquery trông giống như:

$(document).ready(function() { 
    AddAntiForgeryToken = function (data) { 
     data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val(); 
     return data; 
    }; 

    var viewmodel = function() { 
     var vm = this; 

     vm.signedIn = ko.observable(false); 

     vm.signIn = function() { 
      $.post('Home/SignIn', function() { 
       vm.signedIn(true); 
       $.get('Home/GetAuthToken', function (newToken) { 
        $('#__AjaxAntiForgeryForm').html(newToken); 
       }); 
      }); 

     }; 
     vm.signOut = function() { 
      $.post('Home/SignOut', function() { 
       vm.signedIn(false); 
       $.get('Home/GetAuthToken', function (newToken) { 
        $('#__AjaxAntiForgeryForm').html(newToken); 
       }); 
      }); 
     }; 
     vm.testToken = function() { 
      $.post('Home/TestToken', AddAntiForgeryToken({ stuff: 'stuff' })); 
     }; 
    }; 

    ko.applyBindings(new viewmodel(), $('#loginTestView')[0]); 
}); 

Điều chính cần chú ý ở đây là $ .get cần xảy ra sau $ .post để đăng nhập/đăng xuất. Mã này có thể được làm sạch một chút, nhưng đó là chính lấy đi. Nếu bạn không làm như vậy vì yêu cầu không đồng bộ thì $ .get có thể (và có thể sẽ) quay trở lại trước khi bạn thực sự đăng nhập.

Điều đó nên thực hiện. Tôi đã không chạy vào bất kỳ thời điểm nào khác khi mã thông báo được cập nhật nhưng nó sẽ chỉ yêu cầu một cuộc gọi khác để cập nhật một phần.

+0

Giải pháp tuyệt vời @rball, tôi sẽ thử (sẽ mất một chút thời gian). –

Các vấn đề liên quan