この投稿の以下のコードを使用しています:
まず、コントローラーアクションの正しい値を配列変数に入力します。
以下のコードを使用すると、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(); }
});
}
_
MVC 4以降、ValidationHttpRequestWrapperソリューションは必要ありません。これによると link です。
私の解決策は次のとおりです。
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);
}
間違っているのは、このリクエストを処理することになっているコントローラーアクションで、_[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
_の値)が必要です。
私は現在のプロジェクトでこの実際の問題を実装していました。認証されたユーザーを必要とするすべての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パラメーターが追加されていることを簡単に確認できます。
$。ajax のtraditional
属性を設定して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)
{
}
タイプcontentType: 'application/json;のコンテンツは検証できません。 charset = utf-8 '。日付はリクエストのFormプロパティではなく、InputStreamプロパティにアップロードされ、このRequest.Form ["__ RequestVerificationToken"]を持たないためです。
これは常に空であり、検証は失敗します。
JSONオブジェクトにトークンを保持し、ValidateAntiForgeryTokenクラスを変更して、投稿がjsonのときに Request オブジェクトの InputStream を確認することになりました。私は ブログ投稿 について書いていますが、うまくいけばあなたはそれが役に立つと思うかもしれません。
投稿されたJSONを受け取ったときに、AntiForgeryTokenを検証する必要はありません。
理由は、CSRFを防ぐためにAntiForgeryTokenが作成されているためです。 AJAXデータを別のホストに投稿できず、HTMLフォームはリクエスト本文としてJSONを送信できないため、投稿されたJSONからアプリを保護する必要はありません。
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);
}
}
私のためのこの仕事:)
Dixinのブログ をご覧ください。
また、$。ajaxの代わりに$ .postを使用してみませんか?
そのページのjQueryプラグインに加えて、次のような簡単なことができます。
data = $.appendAntiForgeryToken(data,null);
$.post(url, data, function() { refresh(); }, "json");
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);
}
お役に立てれば :)
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に属性を追加する必要はありません)。必要に応じて追加できます。
残念なことに、他の回答は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);
}
});
`` `