私はJSONベースのAJAXリクエストを作成しています。MVCコントローラーを使用すると、Phil Haackの AJAX によるCSRFの防止に非常に感謝しています。 Johan Driessen MVC 4 RC用に更新されたAnti-XSRF しかし、API中心のコントローラーをWeb APIに移行すると、2つのアプローチ間の機能が著しく低下するという問題が発生します。異なるため、CSRFコードを移行できません。
ScottSは最近、同様の 質問 を提起しましたが、これはDarin Dimitrovによって 回答 されました。 Darinのソリューションには、AntiForgery.Validateを呼び出す承認フィルターの実装が含まれます。残念ながら、このコードは私には機能せず(次の段落を参照)、正直なところ、私には高度すぎます。
私が理解しているように、Philのソリューションは、フォーム要素がない状態でJSONリクエストを行うときのMVCANtiForgeryの問題を克服します。フォーム要素は、AntiForgery.Validateメソッドによって想定/予期されます。私は信じていますこれが私がダリンの解決策にも問題を抱えている理由かもしれません。 HttpAntiForgeryException「必要な偽造防止フォームフィールド '__RequestVerificationToken'が存在しません」を受け取ります。トークンがPOSTされていることは確かです(Phil Haackのソリューションによるヘッダーにありますが)。クライアントの呼び出しのスナップショットは次のとおりです。
$token = $('input[name=""__RequestVerificationToken""]').val();
$.ajax({
url:/api/states",
type: "POST",
dataType: "json",
contentType: "application/json: charset=utf-8",
headers: { __RequestVerificationToken: $token }
}).done(function (json) {
...
});
JohanのソリューションとDarinのソリューションを組み合わせてハックを試み、動作させることはできましたが、HttpContext.Currentを導入しています。これが適切/安全かどうか、および提供されたHttpActionContextを使用できない理由がわかりません。
これが私のエレガントでないマッシュアップです。変更はtryブロックの2行です。
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
try
{
var cookie = HttpContext.Current.Request.Cookies[AntiForgeryConfig.CookieName];
AntiForgery.Validate(cookie != null ? cookie.Value : null, HttpContext.Current.Request.Headers["__RequestVerificationToken"]);
}
catch
{
actionContext.Response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
};
return FromResult(actionContext.Response);
}
return continuation();
}
私の質問は次のとおりです。
前もって感謝します!
ヘッダーから読み取ってみることができます。
_var headers = actionContext.Request.Headers;
var cookie = headers
.GetCookies()
.Select(c => c[AntiForgeryConfig.CookieName])
.FirstOrDefault();
var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault();
AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
_
注:GetCookies
は、_System.Net.Http.Formatting.dll
_の一部であるクラスHttpRequestHeadersExtensions
に存在する拡張メソッドです。ほとんどの場合、C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll
に存在します
ActionFilterAttributeから継承し、OnActionExecutingメソッドをオーバーライドすることで少し単純化しましたが、このアプローチが私にも機能することを追加したかっただけです(.ajaxはJSONをWeb APIエンドポイントに投稿します)。
public class ValidateJsonAntiForgeryTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
try
{
var cookieName = AntiForgeryConfig.CookieName;
var headers = actionContext.Request.Headers;
var cookie = headers
.GetCookies()
.Select(c => c[AntiForgeryConfig.CookieName])
.FirstOrDefault();
var rvt = headers.GetValues("__RequestVerificationToken").FirstOrDefault();
AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
}
catch
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Unauthorized request.");
}
}
}
誰かに役立つ場合、.netコアでは、ヘッダーのデフォルト値は実際には「__」なしの「RequestVerificationToken」です。したがって、代わりにヘッダーのキーをそれに変更すると、機能します。
必要に応じて、ヘッダー名を上書きすることもできます。
services.AddAntiforgery(o => o.HeaderName = "__RequestVerificationToken")
ヘッダーの存在をチェックする、Darinの回答を使用した拡張メソッド。このチェックは、結果のエラーメッセージが、「指定されたヘッダーが見つかりませんでした」よりも、何が問題であるか(「必要な偽造防止フォームフィールド「__RequestVerificationToken」が存在しません。」)を示していることを意味します。
public static bool IsHeaderAntiForgeryTokenValid(this HttpRequestMessage request)
{
try
{
HttpRequestHeaders headers = request.Headers;
CookieState cookie = headers
.GetCookies()
.Select(c => c[AntiForgeryConfig.CookieName])
.FirstOrDefault();
var rvt = string.Empty;
if (headers.Any(x => x.Key == AntiForgeryConfig.CookieName))
rvt = headers.GetValues(AntiForgeryConfig.CookieName).FirstOrDefault();
AntiForgery.Validate(cookie != null ? cookie.Value : null, rvt);
}
catch (Exception ex)
{
LogHelper.LogError(ex);
return false;
}
return true;
}
ApiControllerの使用法:
public IHttpActionResult Get()
{
if (Request.IsHeaderAntiForgeryTokenValid())
return Ok();
else
return BadRequest();
}
AuthorizeAttributeを使用した実装:
using System;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Helpers;
using System.Web.Http;
using System.Web.Http.Controllers;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ApiValidateAntiForgeryToken : AuthorizeAttribute {
public const string HeaderName = "X-RequestVerificationToken";
private static string CookieName => AntiForgeryConfig.CookieName;
public static string GenerateAntiForgeryTokenForHeader(HttpContext httpContext) {
if (httpContext == null) {
throw new ArgumentNullException(nameof(httpContext));
}
// check that if the cookie is set to require ssl then we must be using it
if (AntiForgeryConfig.RequireSsl && !httpContext.Request.IsSecureConnection) {
throw new InvalidOperationException("Cannot generate an Anti Forgery Token for a non secure context");
}
// try to find the old cookie token
string oldCookieToken = null;
try {
var token = httpContext.Request.Cookies[CookieName];
if (!string.IsNullOrEmpty(token?.Value)) {
oldCookieToken = token.Value;
}
}
catch {
// do nothing
}
string cookieToken, formToken;
AntiForgery.GetTokens(oldCookieToken, out cookieToken, out formToken);
// set the cookie on the response if we got a new one
if (cookieToken != null) {
var cookie = new HttpCookie(CookieName, cookieToken) {
HttpOnly = true,
};
// note: don't set it directly since the default value is automatically populated from the <httpCookies> config element
if (AntiForgeryConfig.RequireSsl) {
cookie.Secure = AntiForgeryConfig.RequireSsl;
}
httpContext.Response.Cookies.Set(cookie);
}
return formToken;
}
protected override bool IsAuthorized(HttpActionContext actionContext) {
if (HttpContext.Current == null) {
// we need a context to be able to use AntiForgery
return false;
}
var headers = actionContext.Request.Headers;
var cookies = headers.GetCookies();
// check that if the cookie is set to require ssl then we must honor it
if (AntiForgeryConfig.RequireSsl && !HttpContext.Current.Request.IsSecureConnection) {
return false;
}
try {
string cookieToken = cookies.Select(c => c[CookieName]).FirstOrDefault()?.Value?.Trim(); // this throws if the cookie does not exist
string formToken = headers.GetValues(HeaderName).FirstOrDefault()?.Trim();
if (string.IsNullOrEmpty(cookieToken) || string.IsNullOrEmpty(formToken)) {
return false;
}
AntiForgery.Validate(cookieToken, formToken);
return base.IsAuthorized(actionContext);
}
catch {
return false;
}
}
}
次に、コントローラーまたはメソッドを[ApiValidateAntiForgeryToken]で装飾します。
そして、これをかみそりファイルに追加して、javascriptのトークンを生成します。
<script>
var antiForgeryToken = '@ApiValidateAntiForgeryToken.GenerateAntiForgeryTokenForHeader(HttpContext.Current)';
// your code here that uses such token, basically setting it as a 'X-RequestVerificationToken' header for any AJAX calls
</script>