2012-07-30 22 views
19

私はJSONベースのAJAXリクエストを作成していますが、MVCコントローラはPhil HaackにPreventing CSRF with AJAXとJohan DriessenのUpdated Anti-XSRF for MVC 4 RCに対して非常に感謝しています。しかし、API中心のコントローラーをWeb APIに移行すると、2つのアプローチ間の機能が著しく異なり、CSRFコードを移行できない問題が発生しています。MVCでWeb APIのValidatingAntiForgeryToken属性を実装する際の問題4 RC

ScottSは最近、同様のquestionを提出しました。これはDarin Dimitrovによってansweredでした。 Darinのソリューションには、AntiForgery.Validateを呼び出す認証フィルタを実装することが含まれます。残念ながら、このコードは私のためには機能しません(次の段落を参照してください)。そして、正直なところ、私にとっては高度です。

私が理解しているように、Philのソリューションは、フォーム要素がなくてもJSON要求を行うときにMVC AntiForgeryの問題を克服します。フォーム要素は、AntiForgery.Validateメソッドによって想定/予期されます。私これは私もダーリンのソリューションに問題があるかもしれないと信じている。私はHttpAntiForgeryExceptionを受け取る "必要な偽造防止フォームフィールド '__RequestVerificationToken'が存在しない。私はトークンがPOSTされていることを確信しています(Phil Haackの解決策のヘッダーにもかかわらず)。ここでは、クライアントのコールのスナップショットがあります:私は一緒にダーリンさんとヨハンのソリューションを叩解してハックを試してみましたが、作業のものを得ることができたが、私はなぜこれが安全な/適切であるかどうかわからない、HttpContext.Current導入しています

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

提供された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(); 
} 

私の質問は以下のとおりです。

  • 私はダーリンのソリューションが想定する考えで正しいアムフォーム要素の存在?
  • JohanのMVC 4 RCコードでDarinのWeb APIフィルタをマッシュアップするには、どのような方法がありますか?

ありがとうございます!

+0

FromResultは何ですか? –

答えて

31

あなたは、ヘッダーからの読み取りを試みることができる:

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

注:GetCookiesSystem.Net.Http.Formatting.dllの一部であるクラスHttpRequestHeadersExtensionsに存在する拡張メソッドです。これは、最も可能性が高いだけで、私はそれをActionFilterAttributeから継承し、OnActionExecutingメソッドをオーバーライドすることによってビットを単純化するが、このアプローチは、(ウェブAPIエンドポイントにJSONを投稿アヤックス)も私のために働いたことを追加したいC:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll

+1

ねえ... 'GetCookies()'メソッドはどうですか?それを一般的にどのように実装する必要がありますか? –

+1

@ Metropolitanの回答ノートを参照 – Simon

+0

[MVCソースコード](http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenStore.cs)から、私は見ましたGetFormTokenメソッドで 'httpContext.Request.Form [_config.FormFieldName]'を使用していることを示します。フォームとヘッダーの両方の値にアクセスするためにRequest.Paramsを使用しない理由はありますか?しかし、フォームが利用できないときにリクエストヘッダーをチェックすることもできます。 –

12

に存在しています。

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."); 
     } 
    } 
} 
+6

なぜdownvoteですか?私は応答者ではありませんが、downvotesはコメントを保証します。 – DarrellNorton

0

拡張メソッドは、ダリンの答えを使用し、ヘッダーの有無をチェックします。このチェックは、結果のエラーメッセージが何が間違っているかを示していることを意味します(「必要な偽造防止フォームフィールド「__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

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> 
関連する問題