web-dev-qa-db-fra.com

Comment puis-je fournir un AntiForgeryToken lors de la publication de données JSON avec $ .ajax?

J'utilise le code ci-dessous de ce post:

Tout d'abord, je vais remplir une variable de tableau avec les valeurs correctes pour l'action du contrôleur.

En utilisant le code ci-dessous, je pense que cela devrait être très simple en ajoutant simplement la ligne suivante au code JavaScript:

data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();

<%= Html.AntiForgeryToken() %> est à sa place et l'action a un [ValidateAntiForgeryToken]

Mais mon contrôleur continue de dire: "Jeton de contrefaçon non valide"

Qu'est-ce que je fais mal ici?

Code

data["fiscalyear"] = fiscalyear;
data["subgeography"] = $(list).parent().find('input[name=subGeography]').val();
data["territories"] = new Array();

$(items).each(function() {
    data["territories"].Push($(this).find('input[name=territory]').val());
});

    if (url != null) {
        $.ajax(
        {
            dataType: 'JSON',
            contentType: 'application/json; charset=utf-8',
            url: url,
            type: 'POST',
            context: document.body,
            data: JSON.stringify(data),
            success: function() { refresh(); }
        });
    }
67
HerbalMart

Vous n'avez pas besoin de la solution ValidationHttpRequestWrapper depuis MVC 4. Selon ce lien link .

  1. Placez le jeton dans les en-têtes.
  2. Créez un filtre.
  3. Mettez l'attribut sur votre méthode.

Voici ma solution:

var token = $('input[name="__RequestVerificationToken"]').val();
var headers = {};
headers['__RequestVerificationToken'] = token;
$.ajax({
    type: 'POST',
    url: '/MyTestMethod',
    contentType: 'application/json; charset=utf-8',
    headers: headers,
    data: JSON.stringify({
        Test: 'test'
    }),
    dataType: "json",
    success: function () {},
    error: function (xhr) {}
});


[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        var httpContext = filterContext.HttpContext;
        var cookie = httpContext.Request.Cookies[AntiForgeryConfig.CookieName];
        AntiForgery.Validate(cookie != null ? cookie.Value : null, httpContext.Request.Headers["__RequestVerificationToken"]);
    }
}


[HttpPost]
[AllowAnonymous]
[ValidateJsonAntiForgeryToken]
public async Task<JsonResult> MyTestMethod(string Test)
{
    return Json(true);
}
55
Ken Q

Ce qui ne va pas, c’est que l’action du contrôleur censée traiter cette demande et qui est marquée avec le [ValidateAntiForgeryToken] s'attend à ce qu’un paramètre appelé __RequestVerificationToken soit POSTé avec la demande. 

Il n'existe pas de paramètre POSTed tel que vous utilisez JSON.stringify(data) qui convertit votre formulaire en sa représentation JSON et donc l'exception est levée.

Donc, je peux voir deux solutions possibles ici:

Numéro 1: Utilisez x-www-form-urlencoded au lieu de JSON pour envoyer les paramètres de votre demande:

data["__RequestVerificationToken"] = $('[name=__RequestVerificationToken]').val();
data["fiscalyear"] = fiscalyear;
// ... other data if necessary

$.ajax({
    url: url,
    type: 'POST',
    context: document.body,
    data: data,
    success: function() { refresh(); }
});

Numéro 2: séparez la demande en deux paramètres:

data["fiscalyear"] = fiscalyear;
// ... other data if necessary
var token = $('[name=__RequestVerificationToken]').val();

$.ajax({
    url: url,
    type: 'POST',
    context: document.body,
    data: { __RequestVerificationToken: token, jsonRequest: JSON.stringify(data) },
    success: function() { refresh(); }
});

Donc, dans tous les cas, vous devez POST] la valeur __RequestVerificationToken.

46
Darin Dimitrov

Je ne faisais que mettre en œuvre ce problème dans mon projet actuel. Je l'ai fait pour tous les POST Ajax qui avaient besoin d'un utilisateur authentifié.

Tout d’abord, j’ai décidé de raccrocher mes appels jQuery Ajax afin que je ne me répète pas trop souvent. Cet extrait de code JavaScript garantit que tous les appels ajax (post) ajouteront le jeton de validation de ma demande à la demande. Remarque: le nom __RequestVerificationToken est utilisé par le framework .NET afin que je puisse utiliser les fonctionnalités standard Anti-CSRF comme indiqué ci-dessous.

$(document).ready(function () {
    securityToken = $('[name=__RequestVerificationToken]').val();
    $('body').bind('ajaxSend', function (Elm, xhr, s) {
        if (s.type == 'POST' && typeof securityToken != 'undefined') {
            if (s.data.length > 0) {
                s.data += "&__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
            else {
                s.data = "__RequestVerificationToken=" + encodeURIComponent(securityToken);
            }
        }
    });
});

Dans vos vues où vous avez besoin que le jeton soit disponible pour le code JavaScript ci-dessus, utilisez simplement le HTML-Helper commun. Vous pouvez fondamentalement ajouter ce code où vous voulez. Je l'ai placé dans une instruction if (Request.IsAuthenticated):

@Html.AntiForgeryToken() // You can provide a string as salt when needed which needs to match the one on the controller

Dans votre contrôleur, utilisez simplement le mécanisme anti-CSRF standard ASP.NET MVC. Je l'ai fait comme ça (même si j'ai utilisé un sel).

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public JsonResult SomeMethod(string param)
{
    // Do something
    return Json(true);
}

Avec Firebug ou un outil similaire, vous pouvez facilement voir comment vos demandes POST ont maintenant un paramètre __RequestVerificationToken ajouté.

11
360Airwalk

Vous pouvez définir l'attribut traditional de $ .ajax / et le définir sur true, afin d'envoyer les données Json sous forme codée d'URL. Assurez-vous de définir type:'POST'. Avec cette méthode, vous pouvez même envoyer des tableaux et vous ne devez pas utiliser JSON.stringyfy ni aucune modification du côté serveur (par exemple, créer des attributs personnalisés pour extraire l'en-tête).

J'ai essayé ceci sur ASP.NET MVC3 et jquery 1.7 et ça marche

voici l'extrait de code.

var data = { items: [1, 2, 3], someflag: true};

data.__RequestVerificationToken = $(':input[name="__RequestVerificationToken"]').val();

$.ajax({
    url: 'Test/FakeAction'
    type: 'POST',
    data: data
    dataType: 'json',
    traditional: true,
    success: function (data, status, jqxhr) {
        // some code after succes
    },
    error: function () {
        // alert the error
    }
});

Cela correspondra à l'action MVC avec la signature suivante

[HttpPost]
[Authorize]
[ValidateAntiForgeryToken]
public ActionResult FakeAction(int[] items, bool someflag)
{
}
7
N30

Vous ne pouvez pas valider un contenu de type contentType: 'application/json; charset = utf-8 'car votre date ne sera pas téléchargée dans la propriété Form de la requête, mais dans la propriété InputStream et vous n'aurez jamais ce Request.Form ["__ RequestVerificationToken"].

Ce sera toujours vide et la validation échouera.

5
Vasioky

Je tiens le jeton dans mon objet JSON et j'ai fini par modifier la classe ValidateAntiForgeryToken pour vérifier le InputStream de l'objet Request lorsque le message est json. J'ai écrit un blog post à ce sujet, j'espère que vous le trouverez utile.

5
TWith2Sugars

Vous n'aurez jamais à valider un AntiForgeryToken lorsque vous recevez du JSON publié.

La raison en est que AntiForgeryToken a été créé pour empêcher CSRF. Étant donné que vous ne pouvez pas publier de données AJAX sur un autre hôte et que les formulaires HTML ne peuvent pas soumettre de code JSON en tant que corps de la demande, vous n'avez pas à protéger votre application contre le format JSON publié.

5
Antoine Leclair

Consultez le blog de Dixin pour un excellent article sur ce sujet. 

Aussi, pourquoi ne pas utiliser $ .post au lieu de $ .ajax?

Avec le plugin jQuery sur cette page, vous pouvez ensuite faire quelque chose d'aussi simple que:

        data = $.appendAntiForgeryToken(data,null);

        $.post(url, data, function() { refresh(); }, "json");
2
AlDev

Je l'ai résolu globalement avec RequestHeader.

$.ajaxPrefilter(function (options, originalOptions, jqXhr) {
    if (options.type.toUpperCase() === "POST") {
        // We need to add the verificationToken to all POSTs
        if (requestVerificationTokenVariable.length > 0)
            jqXhr.setRequestHeader("__RequestVerificationToken", requestVerificationTokenVariable);
    }
});

où requestVerificationTokenVariable est une chaîne de variable contenant la valeur de jeton . Tous les appels ajax envoient ensuite le jeton au serveur, mais la valeur par défaut ValidateAntiForgeryTokenAttribute obtient la valeur Request.Form . jeton d'en-tête à request.form, que je peux utiliser le défaut ValidateAntiForgeryTokenAttribute:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
      filters.Add(new GlobalAntiForgeryTokenAttribute(false));
}


public class GlobalAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
    private readonly bool autoValidateAllPost;

    public GlobalAntiForgeryTokenAttribute(bool autoValidateAllPost)
    {
        this.autoValidateAllPost = autoValidateAllPost;
    }

    private const string RequestVerificationTokenKey = "__RequestVerificationToken";
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        var req = filterContext.HttpContext.Request;
        if (req.HttpMethod.ToUpperInvariant() == "POST")
        {
            //gestione per ValidateAntiForgeryToken che gestisce solo il recupero da Request.Form (non disponibile per le chiamate ajax json)
            if (req.Form[RequestVerificationTokenKey] == null && req.IsAjaxRequest())
            {
                var token = req.Headers[RequestVerificationTokenKey];
                if (!string.IsNullOrEmpty(token))
                {
                    req.Form.SetReadOnly(false);
                    req.Form[RequestVerificationTokenKey] = token;
                    req.Form.SetReadOnly(true);
                }
            }

            if (autoValidateAllPost)
                AntiForgery.Validate();
        }
    }
}

public static class NameValueCollectionExtensions
{
    private static readonly PropertyInfo NameObjectCollectionBaseIsReadOnly = typeof(NameObjectCollectionBase).GetProperty("IsReadOnly", BindingFlags.FlattenHierarchy | BindingFlags.NonPublic | BindingFlags.Instance);

    public static void SetReadOnly(this NameValueCollection source, bool readOnly)
    {
        NameObjectCollectionBaseIsReadOnly.SetValue(source, readOnly);
    }
}

Ce travail pour moi :)

2
Davide Ciarmiello

La publication de modèles basée sur AJAX avec AntiForgerytoken peut être simplifiée avec la bibliothèque Newtonsoft.JSON
L'approche ci-dessous a fonctionné pour moi:
Gardez votre message AJAX comme ceci:

    $.ajax(
    {
        dataType: 'JSON',
        url: url,
        type: 'POST',
        context: document.body,
        data: {
                 '__RequestVerificationToken' : token,
                  'model_json': JSON.stringify(data)
        }; ,
        success: function() { refresh(); }
    });

Puis dans votre action MVC:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Edit(FormCollection data)
    {
        var model= JsonConvert.DeserializeObject<Order>(data["model_json"]);
        return Json(1);
    }

J'espère que cela t'aides :) 

1
Niraj Kale

Je devais être un peu sournois pour valider les jetons anti-contrefaçon lors de la publication de JSON, mais cela a fonctionné.

//If it's not a GET, and the data they're sending is a string (since we already had a separate solution in place for form-encoded data), then add the verification token to the URL, if it's not already there.
$.ajaxSetup({
    beforeSend: function (xhr, options) {
        if (options.type && options.type.toLowerCase() !== 'get' && typeof (options.data) === 'string' && options.url.indexOf("?__RequestVerificationToken=") < 0 && options.url.indexOf("&__RequestVerificationToken=") < 0) {
            if (options.url.indexOf('?') < 0) {
                options.url += '?';
            }
            else {
                options.url += '&';
            }
            options.url += "__RequestVerificationToken=" + encodeURIComponent($('input[name=__RequestVerificationToken]').val());
        }
    }
});

Mais, comme l'ont déjà mentionné quelques personnes, la validation vérifie uniquement le formulaire, pas JSON, ni la chaîne de requête. Nous avons donc surchargé le comportement de l'attribut. Réimplémenter toutes les validations aurait été terrible (et probablement pas sécurisé). Je viens donc de surcharger la propriété Form afin que, si le jeton était passé dans QueryString, la validation intégrée THINK se trouvait dans le formulaire.

C'est un peu délicat, car le formulaire est en lecture seule, mais faisable.

    if (IsAuth(HttpContext.Current) && !IsGet(HttpContext.Current))
    {
        //if the token is in the params but not the form, we sneak in our own HttpContext/HttpRequest
        if (HttpContext.Current.Request.Params != null && HttpContext.Current.Request.Form != null
            && HttpContext.Current.Request.Params["__RequestVerificationToken"] != null && HttpContext.Current.Request.Form["__RequestVerificationToken"] == null)
        {
            AntiForgery.Validate(new ValidationHttpContextWrapper(HttpContext.Current), null);
        }
        else
        {
            AntiForgery.Validate(new HttpContextWrapper(HttpContext.Current), null);
        }
    }

    //don't validate un-authenticated requests; anyone could do it, anyway
    private static bool IsAuth(HttpContext context)
    {
        return context.User != null && context.User.Identity != null && !string.IsNullOrEmpty(context.User.Identity.Name);
    }

    //only validate posts because that's what CSRF is for
    private static bool IsGet(HttpContext context)
    {
        return context.Request.HttpMethod.ToUpper() == "GET";
    }

...

internal class ValidationHttpContextWrapper : HttpContextBase
{
    private HttpContext _context;
    private ValidationHttpRequestWrapper _request;

    public ValidationHttpContextWrapper(HttpContext context)
        : base()
    {
        _context = context;
        _request = new ValidationHttpRequestWrapper(context.Request);
    }

    public override HttpRequestBase Request { get { return _request; } }

    public override IPrincipal User
    {
        get { return _context.User; }
        set { _context.User = value; }
    }
}

internal class ValidationHttpRequestWrapper : HttpRequestBase
{
    private HttpRequest _request;
    private System.Collections.Specialized.NameValueCollection _form;

    public ValidationHttpRequestWrapper(HttpRequest request)
        : base()
    {
        _request = request;
        _form = new System.Collections.Specialized.NameValueCollection(request.Form);
        _form.Add("__RequestVerificationToken", request.Params["__RequestVerificationToken"]);
    }

    public override System.Collections.Specialized.NameValueCollection Form { get { return _form; } }

    public override string ApplicationPath { get { return _request.ApplicationPath; } }
    public override HttpCookieCollection Cookies { get { return _request.Cookies; } }
}

Il y a quelques autres choses qui diffèrent dans notre solution (en particulier, nous utilisons un HttpModule afin que nous n'ayons pas à ajouter l'attribut à chaque POST) que j'ai laissées de côté en faveur de la brièveté. Je peux l'ajouter si nécessaire.

1
DrShaffopolis

Malheureusement pour moi, les autres réponses reposent sur un formatage de requête géré par jquery, et aucune d’entre elles ne fonctionnait lors de la définition directe de la charge utile. (Pour être juste, le mettre dans l'en-tête aurait fonctionné, mais je ne voulais pas emprunter cette voie.)

Pour accomplir ceci dans la fonction beforeSend, les travaux suivants. $.params() transforme l'objet en format standard codé sous forme/url. 

J'avais essayé toutes sortes de variantes de stringing JSON avec le jeton et aucune d'entre elles ne fonctionnait. 

$.ajax({
...other params...,
beforeSend: function(jqXHR, settings){

    var token = ''; //get token

    data = {
        '__RequestVerificationToken' : token,
        'otherData': 'value'
     }; 
    settings.data = $.param(data);
    }
});

`` `

0
emragins