MVC3 AntiForgeryToken ломается при входе в Ajax - PullRequest
8 голосов
/ 17 октября 2011

ASP.NET MVC AntiForgeryToken основан на текущем HttpContext.User.Это значение используется для создания токена при вызове Html.AntiForgeryToken().В принципе это нормально (см. Объяснение в последнем абзаце здесь ), но проблема возникает, когда вы входите через Ajax-вызов .

В моем коде, когдапользователь входит в систему, учетные данные отправляются как объект Json в Ajax (скрытое значение поля AntiForgeryToken также отправляется внутри Json), сервер аутентифицирует пользователя, применяет FormsAuthentication.SetAuthCookie () и возвращает результат Json, которыйсодержит некоторые пользовательские данные.Таким образом, я могу избежать полного обновления страницы при входе в систему.

Проблема заключается в том, что каждый последующий Ajax-запрос к серверу теперь не выполняется при ValidateAntiForgeryTokenAttribute, поскольку теперь он ожидает токен защиты от подделки, который несовместим сфайл cookie для защиты от подделки.

Как получить действительный токен для защиты от подделки, чтобы поместить его в скрытое поле клиента, чтобы каждый запрос Json после входа в систему выполнялся успешно?

Я попытался получить новый токен скрытого поля вручную (используя AntiForgery.GetHtml() в действии, извлекая саму строку токена, возвращая ее клиенту в Json и помещая ее в скрытое поле защиты от подделки вручную в JavaScript), но это не помоглоне работает - последующий вызов Ajax не выполняется на ValidateAntiForgeryTokenAttribute на сервере.Фактически, каждый вызов AntiForgery.GetHtml() (что, в сущности, и делает Html.AntiForgeryToken() помощник) создает другой токен, который делает недействительным предыдущий.

Я также пытался установить HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null); как подробно здесь , но это не работает.

Примечание: Это решение не работает для меня из-за моей конкретной ситуации: Ajax login , который изменяет идентификатор пользователя на сервере и, следовательно, каждый токен, который был сгенерирован до входа в систему, является недействительным; это решение также не применяется, потому что оно решает другую проблему.

Ответы [ 2 ]

6 голосов
/ 18 октября 2011

Вам потребуется очистить и повторить любой существующий токен формы, который у вас есть при входе в систему.Это означает, что ваш логин-код должен будет либо обновить текущую страницу (вроде как убивает ajax-часть, а), либо вашу собственную реализацию токена, либо вам нужно будет обновить свой токен.Можно запросить частичное представление, извлечь токен и обновить вашу форму.На самом деле у вас может быть спокойный URL, который не возвращает ничего, кроме токена аутентифицированному пользователю.Кто-то может возразить, что это проблема безопасности, но я не верю в это, потому что это просто более простой способ получить токен, чем запрашивать какое-либо представление - частичное или иное.

Вы должны иметь возможность легко получитьэкземпляры токена для замены через:

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

РЕДАКТИРОВАТЬ После повторного чтения еще несколько раз - я задаю вопрос

Зачем вам токен в форме, если пользователь не вошел в системуin. Вы разрешаете использовать одну и ту же форму, когда вы не вошли в систему и не вошли в систему?Большинство сайтов в сети даже в этом случае будут перенаправлены для входа в систему.Я правильно понимаю?Если это так, вы можете пропустить токен здесь или использовать токен второго типа для неаутентифицированных пользователей.Вы, я полагаю, говорите, что неаутентифицированный пользователь уже может что-то подать в приложении - опять же, если я правильно понимаю - без аутентификации.

3 голосов
/ 22 августа 2012

Хорошо, я решил объединить ответ отсюда: jQuery Ajax-вызовы и Html.AntiForgeryToken () с частичным.Я использую нокаут, но для тех из вас, кто не знаком с ним, вы все равно сможете довольно легко следовать за ним.

Сначала мой html:

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

Главное отличие в том, что вместо этогоиз @ Html.AntiForgeryToken () У меня есть часть AntiForgeryToken, которая содержит @ Html.AntiForgeryToken ().

Поэтому, чтобы действительно уточнить, у меня теперь есть файл AntiForgeryToken.cshtml, содержащий всего лишь:

@Html.AntiForgeryToken()

Теперь, когда вы входите / выходите, вам нужно обновить токен, чтобы javascript / jquery выглядел так:

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

Главное, на что следует обратить внимание, это то, что $ .get должен произойти после$ .post для входа / выхода.Этот код можно немного почистить, но это главное.Если вы этого не сделаете, так как запросы асинхронные, $ .get может (и, вероятно, будет) возвращаться до того, как вы действительно войдете в систему.

Это должно сделать это.Я не сталкивался с другими случаями, когда токен обновлялся, но для обновления партиала просто потребовался бы еще один вызов.

...