web-dev-qa-db-ja.com

$ .ajaxを使用してJSONデータを送信するときにAntiForgeryTokenを提供するにはどうすればよいですか?

この投稿の以下のコードを使用しています:

まず、コントローラーアクションの正しい値を配列変数に入力します。

以下のコードを使用すると、JavaScriptコードに次の行を追加するだけで非常に簡単になるはずです。

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

<%= Html.AntiForgeryToken() %>は適切な場所にあり、アクションには_[ValidateAntiForgeryToken]_があります

しかし、コントローラーのアクションは「無効な偽造トークン」と言い続けます。

ここで何が間違っていますか?

コード

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

MVC 4以降、ValidationHttpRequestWrapperソリューションは必要ありません。これによると link です。

  1. ヘッダーにトークンを入れます。
  2. フィルターを作成します。
  3. メソッドに属性を追加します。

私の解決策は次のとおりです。

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);
}
57
Ken Q

間違っているのは、このリクエストを処理することになっているコントローラーアクションで、_[ValidateAntiForgeryToken]_でマークされているものは、リクエストとともに___RequestVerificationToken_というパラメーターがPOSTされることを期待していることです。

フォームをJSON表現に変換するJSON.stringify(data)を使用しているため、そのようなパラメーターはPOSTされないため、例外がスローされます。

だから私はここで2つの可能な解決策を見ることができます:

番号1:リクエストパラメータの送信にJSONの代わりに_x-www-form-urlencoded_を使用します。

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

番号2:リクエストを2つのパラメーターに分割します。

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

そのため、すべての場合にPOST ___RequestVerificationToken_の値)が必要です。

48
Darin Dimitrov

私は現在のプロジェクトでこの実際の問題を実装していました。認証されたユーザーを必要とするすべてのAjax POSTに対してこれを行いました。

まず、jQuery Ajax呼び出しをフックすることで、あまり頻繁に繰り返さないようにしました。このJavaScriptスニペットにより、すべてのajax(post)呼び出しがリクエスト検証トークンをリクエストに追加します。注:名前__RequestVerificationTokenは.NETフレームワークによって使用されるため、以下に示すように標準のCSRF防止機能を使用できます。

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

上記のJavaScriptコードでトークンを使用する必要があるビューでは、一般的なHTMLヘルパーを使用するだけです。基本的にこのコードはどこにでも追加できます。 if(Request.IsAuthenticated)ステートメント内に配置しました。

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

コントローラーでは、標準のASP.NET MVCのCSRF対策メカニズムを使用するだけです。私はこのようにしました(実際に塩を使用しました)。

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

Firebugまたは同様のツールを使用すると、POSTリクエストに__RequestVerificationTokenパラメーターが追加されていることを簡単に確認できます。

11
360Airwalk

$。ajaxtraditional属性を設定してtrueに設定すると、jsonデータをURLエンコード形式で送信できます。必ずtype:'POST'を設定してください。このメソッドを使用すると、配列を送信することもでき、JSON.stringyfyやサーバー側での変更(たとえば、ヘッダーをスニッフィングするカスタム属性の作成)を使用する必要はありません。

ASP.NET MVC3とjquery 1.7セットアップでこれを試しましたが、動作しています

以下はコードスニペットです。

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

これは、次の署名を持つMVCアクションと一致します

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

タイプcontentType: 'application/json;のコンテンツは検証できません。 charset = utf-8 '。日付はリクエストのFormプロパティではなく、InputStreamプロパティにアップロードされ、このRequest.Form ["__ RequestVerificationToken"]を持たないためです。

これは常に空であり、検証は失敗します。

5
Vasioky

JSONオブジェクトにトークンを保持し、ValidateAntiForgeryTokenクラスを変更して、投稿がjsonのときに Request オブジェクトの InputStream を確認することになりました。私は ブログ投稿 について書いていますが、うまくいけばあなたはそれが役に立つと思うかもしれません。

5
TWith2Sugars

投稿されたJSONを受け取ったときに、AntiForgeryTokenを検証する必要はありません。

理由は、CSRFを防ぐためにAntiForgeryTokenが作成されているためです。 AJAXデータを別のホストに投稿できず、HTMLフォームはリクエスト本文としてJSONを送信できないため、投稿されたJSONからアプリを保護する必要はありません。

5
Antoine Leclair

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

requestVerificationTokenVariableは、トークン値を含む変数文字列です。次に、すべてのajax呼び出しはトークンをサーバーに送信しますが、デフォルトのValidateAntiForgeryTokenAttributeはRequest.Form値を取得します。デフォルトのValidateAntiForgeryTokenAttributeを使用できるので、ヘッダーからrequest.formにトークンをコピーするこのglobalFilterを記述して追加しました。

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

私のためのこの仕事:)

3

Dixinのブログ をご覧ください。

また、$。ajaxの代わりに$ .postを使用してみませんか?

そのページのjQueryプラグインに加えて、次のような簡単なことができます。

        data = $.appendAntiForgeryToken(data,null);

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

AntiForgerytokenを使用したAJAXベースのモデル投稿は、Newtonsoft.JSONライブラリを使用して少し簡単にできます。
以下のアプローチがうまくいきました:
AJAXこのように投稿してください:

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

次に、MVCアクションで:

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

お役に立てれば :)

1
Niraj Kale

JSONを投稿する際に偽造防止トークンを検証するために少し恥ずかしがる必要がありましたが、うまくいきました。

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

ただし、すでに述べたように、検証ではフォームのみがチェックされ、JSONではなくクエリ文字列のみがチェックされます。そのため、属性の動作を無効にしました。すべての検証の再実装はひどい(そしておそらく安全ではない)ので、トークンがQueryStringで渡された場合、フォームに組み込みの検証THINKが含まれるようにFormプロパティをオーバーライドしました。

フォームは読み取り専用ですが、実行可能であるため、少し注意が必要です。

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

ソリューションについては他にもいくつかの違いがあります(具体的には、HttpModuleを使用しているため、すべてのPOSTに属性を追加する必要はありません)。必要に応じて追加できます。

1
DrShaffopolis

残念なことに、他の回答はjqueryによって処理されるリクエストのフォーマットに依存しており、ペイロードを直接設定するときに機能しませんでした。 (公平を期すために、ヘッダーにそれを入れればうまくいきますが、私はそのルートに行きたくありませんでした。)

beforeSend関数でこれを実現するには、次のようにします。 $.params()は、オブジェクトを標準形式/ urlエンコード形式に変換します。

Jsonをトークンで文字列化するあらゆる種類のバリエーションを試しましたが、どれも機能しませんでした。

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

    var token = ''; //get token

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

`` `

0
emragins