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?
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(); }
});
}
Vous n'avez pas besoin de la solution ValidationHttpRequestWrapper depuis MVC 4. Selon ce lien link .
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);
}
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
.
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é.
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)
{
}
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.
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.
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é.
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");
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 :)
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 :)
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.
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);
}
});
`` `