2012-07-13 139 views
48

我們有一些現有的MVC Web服務,稱爲AJAX風格的網頁。這些服務利用ValidateAntiForgeryToken屬性來幫助防止請求僞造。Web API和ValidateAntiForgeryToken

我們希望將這些服務遷移到Web API,但似乎沒有等效的防僞功能。

我錯過了什麼嗎?使用Web API解決請求僞造有什麼不同的方法嗎?

+3

雖然Darin的答案是正確的,DazWilkin鉛我們是將標記放入標題的更好解決方案。 http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc/11726560#11726560 – ScottS 2012-08-03 22:41:41

+7

那個更好的解決方案也來自Darin :) – 2013-04-12 14:43:16

回答

44

您可以實現這樣的授權屬性:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 
public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter 
{ 
    public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation) 
    { 
     try 
     { 
      AntiForgery.Validate(); 
     } 
     catch 
     { 
      actionContext.Response = new HttpResponseMessage 
      { 
       StatusCode = HttpStatusCode.Forbidden, 
       RequestMessage = actionContext.ControllerContext.Request 
      }; 
      return FromResult(actionContext.Response); 
     } 
     return continuation(); 
    } 

    private Task<HttpResponseMessage> FromResult(HttpResponseMessage result) 
    { 
     var source = new TaskCompletionSource<HttpResponseMessage>(); 
     source.SetResult(result); 
     return source.Task; 
    } 
} 

,然後用它裝點您的API操作:

[ValidateAntiForgeryToken] 
public HttpResponseMessage Post() 
{ 
    // some work 
    return Request.CreateResponse(HttpStatusCode.Accepted); 
} 
+0

謝謝,System.Web。 Helpers.AntiForgery看起來像是我的問題的答案。 – ScottS 2012-07-14 21:42:48

+0

在我的情況下,數據通過以下代碼作爲JSON字符串,並且上述解決方案無效:$。阿賈克斯({ 網址:網址, 方法: 「PUT」, 的contentType: 「應用/ JSON」, 數據類型: 「JSON」, 數據:formJsonData }) – Tohid 2015-05-21 18:51:25

+0

我想如果你郵寄到這種做法只會工作您的ajax服務使用表單編碼,並將該標記顯式包含在ajax請求表單數據中。如果您想爲您的ajax數據使用JSON,您需要使用自己的代碼來提取令牌並調用AntiForgery.Validate的雙參數重載。你可以發送json數據中的「formToken」參數,或者像這樣的HTTP頭文件:http://stephenwalther.com/archive/2013/03/05/security-issues-with-single-page-apps – Andy 2016-07-07 13:22:57

4

link的幫助下,你可以從剃刀視圖中的防僞標記並將令牌作爲標題傳遞給:

var csrfToken = $("input[name='__RequestVerificationToken']").val(); 
$.ajax({ 
    headers: { __RequestVerificationToken: csrfToken }, 
    type: "POST", 
    dataType: "json", 
    contentType: 'application/json; charset=utf-8', 
    url: "/api/products", 
    data: JSON.stringify({ name: "Milk", price: 2.33 }), 
    statusCode: { 
     200: function() { 
      alert("Success!"); 
     } 
    } 
}); 
+1

「ValidateAjaxAntiForgeryToken僅在用戶通過身份驗證時才起作用,並且不適用於匿名請求。」 – 2017-12-29 16:31:56

18

補充以上代碼 FilterAttribute

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 
    public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter 
    { 
     public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation) 
     { 
      try 
      { 
       string cookieToken = ""; 
       string formToken = ""; 

       IEnumerable<string> tokenHeaders; 
       if (actionContext.Request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders)) 
       { 
        string[] tokens = tokenHeaders.First().Split(':'); 
        if (tokens.Length == 2) 
        { 
         cookieToken = tokens[0].Trim(); 
         formToken = tokens[1].Trim(); 
        } 
       } 
       AntiForgery.Validate(cookieToken, formToken); 
      } 
      catch (System.Web.Mvc.HttpAntiForgeryException e) 
      { 
       actionContext.Response = new HttpResponseMessage 
       { 
        StatusCode = HttpStatusCode.Forbidden, 
        RequestMessage = actionContext.ControllerContext.Request 
       }; 
       return FromResult(actionContext.Response); 
      } 
      return continuation(); 
     } 

     private Task<HttpResponseMessage> FromResult(HttpResponseMessage result) 
     { 
      var source = new TaskCompletionSource<HttpResponseMessage>(); 
      source.SetResult(result); 
      return source.Task; 
     } 

的HTML功能使用剃刀

@functions{ 
    public string TokenHeaderValue() 
     { 
      string cookieToken, formToken; 
      AntiForgery.GetTokens(null, out cookieToken, out formToken); 
      return cookieToken + ":" + formToken; 
     } 
} 

採用了棱角分明

return $http({ 
    method: 'POST', 
    url: '@Url.Content("~/api/invite/")', 
    data: {}, 
    headers: { 
     'RequestVerificationToken': '@TokenHeaderValue()' 
    } 
}); 
+6

你至少應該給予參考。 orignal文章在這裏 http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-攻擊 – 2014-11-02 08:13:24

+0

爲什麼這比回答更好? – 2015-05-11 00:05:31

+3

@伊萬沃伯頓 - 首先因爲它的作品!第二,因爲它使用請求頭來發送令牌,這是比使用cookie更好的標準。 Web API應該是一個RESTful服務,並且不需要cookie就可以工作。 – Tohid 2015-05-21 19:19:36

4

奧斯瓦爾多的答案,但作爲一個AuthorizeAttribute

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 
    public class ApiValidateAntiForgeryToken : AuthorizeAttribute 
    { 
    public static string GenerateAntiForgeryTokenForHeader() { 
     string cookieToken, formToken; 
     AntiForgery.GetTokens(null, out cookieToken, out formToken); 
     return cookieToken + ":" + formToken; 
    } 


    protected override bool IsAuthorized(HttpActionContext actionContext) { 
     var headers = actionContext.Request.Headers; 

     // we pass both the cookie and the form token into a single header field 
     string headerToken = headers.Contains("__RequestVerificationToken") ? headers.GetValues("__RequestVerificationToken").FirstOrDefault() : null; 

     if (headerToken == null) { 
     return false; 
     } 

     string[] tokens = headerToken.Split(':'); 
     if (tokens.Length != 2) { 
     return false; 
     } 

     string cookieToken = tokens[0].Trim(); 
     string formToken = tokens[1].Trim(); 

     try { 
     AntiForgery.Validate(cookieToken, formToken); 
     } 
     catch { 
     return false; 
     } 

     return base.IsAuthorized(actionContext); 
    } 
    } 

實施您可以用[ApiValidateAntiForgeryToken]修飾您的控制器或方法,然後在您的剃鬚刀JavaScript代碼中將該方法的標頭傳遞給RequestVerificationToken:「@ ApiValidateAntiForgeryToken.GenerateAntiForgeryTokenForHeader()」。

+0

嗨,@Javier,請你解釋爲什麼你的'ApiValidateAntiForgeryToken'實現繼承自'AuthorizeAttribute',而不是實現'IAuthorizationFilter'? – 2017-07-11 15:26:41

+1

嗨Gerardo,基本上它是一樣的東西,但以不同的方式實現。請參閱https://stackoverflow.com/questions/27021506/what-is-the-difference-between-using-authorizeattribute- or-iauthorizationfilter - 在我的情況下,我需要它的項目,需要一個AutorizeAttribute,並沒有工作界面(我認爲這是因爲SignalR,但我不記得了)。無論如何,請考慮我在同一篇文章中給出的其他答案,因爲它更安全。 – 2017-07-13 17:57:39

+0

這不適用於匿名請求。 – 2017-12-29 16:32:41

1

考慮到這一點之後,混合cookie和表單標記是一個壞主意,因爲它違背了反僞造標記的整個目的。最好將cookie部分保存爲cookie,同時將表單部分移動到auth頭部,因此這個新的答案(又作爲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>