web-dev-qa-db-ja.com

MVC 4RCを使用したWebAPIのValidatingAntiForgeryToken属性の実装に関する問題

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

私の質問は次のとおりです。

  • ダリンの解はフォーム要素の存在を前提としていると考えるのは正しいですか?
  • ダリンのWebAPIフィルターをヨハンのMVC4 RCコードとマッシュアップするエレガントな方法は何ですか?

前もって感謝します!

19
DazWilkin

ヘッダーから読み取ってみることができます。

_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に存在します

32
Darin Dimitrov

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.");
        }
    }
}
13
III

誰かに役立つ場合、.netコアでは、ヘッダーのデフォルト値は実際には「__」なしの「RequestVerificationToken」です。したがって、代わりにヘッダーのキーをそれに変更すると、機能します。

必要に応じて、ヘッダー名を上書きすることもできます。

services.AddAntiforgery(o => o.HeaderName = "__RequestVerificationToken")

0
JohnnyFun

ヘッダーの存在をチェックする、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();
}
0
jaybro

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>
0
Javier G.