2012-07-13 16 views
48

Abbiamo alcuni servizi Web MVC esistenti denominati stile AJAX dalle pagine Web. Questi servizi utilizzano l'attributo ValidateAntiForgeryToken per aiutare a prevenire le falsificazioni delle richieste.API Web e ValidateAntiForgeryToken

Stiamo cercando di migrare questi servizi alle API Web, ma non sembra esserci alcuna funzionalità anti-contraffazione equivalente.

Mi manca qualcosa? Esiste un approccio diverso per affrontare le falsificazioni delle richieste con l'API Web?

+3

Mentre la risposta di Darin è corretta, piombo DazWilkin noi per essere la soluzione migliore di mettere il token nelle intestazioni. http://stackoverflow.com/questions/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc/11726560#11726560 – ScottS

+7

Questa soluzione migliore è anche da Darin :) –

risposta

44

Si potrebbe implementare tale attributo autorizzazione:

[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; 
    } 
} 

e poi decorare le vostre azioni API con esso:

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

Grazie, System.Web. Helpers.AntiForgery sembra che sarà la risposta al mio problema. – ScottS

+0

nel mio caso, i dati provengono da una stringa JSON tramite il seguente codice e la soluzione di cui sopra non ha funzionato: $.ajax ({ URL: URL, metodo: "put", contentType: "application/json", dataType: "json", i dati : formJsonData }) – Tohid

+0

Penso che questo approccio funziona solo se si posta a il tuo servizio Ajax utilizza la codifica del modulo e include esplicitamente il token nei dati del modulo di richiesta Ajax. Se si desidera utilizzare JSON per i dati Ajax, sarà necessario eseguire il roll-your-own code per estrarre i token e richiamare l'overload a due parametri di AntiForgery.Validate. puoi inviare il parametro "formToken" nei dati JSON o in un'intestazione HTTP come questa http://stephenwalther.com/archive/2013/03/05/security-issues-with-single-page-apps – Andy

4

Questo link aiutato, è possibile recuperare il token anti-contraffazione dal punto di vista del rasoio e passare il token come intestazione:

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 funziona solo quando l'utente è autenticato e non funzionerà per richieste anonime." –

18

Complementare sopra Codice 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; 
     } 

Funzione HTML usando Razor

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

Utilizzando angolare

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

dovresti almeno fare riferimento. L'articolo originale è qui http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks –

+0

Perché è meglio della risposta? –

+3

@IanWarburton: innanzitutto perché funziona! Secondo perché utilizza l'intestazione della richiesta per inviare token, che è uno standard migliore rispetto all'utilizzo dei cookie. L'API Web dovrebbe essere un servizio RESTful e funziona senza cookie. – Tohid

4

risposta di Oswaldo ma implementato come un 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); 
    } 
    } 

Puoi decorare il controller o i metodi con [ApiValidateAntiForgeryToken] e quindi passare RequestVerificationToken: "@ ApiValidateAntiForgeryToken.GenerateAntiForgeryTokenForHeader()" come intestazione per il metodo nel codice javascript del tuo rasoio.

+0

Ciao, @Javier, potresti spiegare perché la tua implementazione di "ApiValidateAntiForgeryToken" è ereditata da "AuthorizeAttribute", invece di implementare 'IAuthorizationFilter'? –

+1

Ciao Gerardo, fondamentalmente è la stessa cosa ma implementato in un modo diverso. Vedere https://stackoverflow.com/questions/27021506/what-is-the-difference-between-using-authorizeattribute-or-airauthorizationfilter - Nel mio caso ne avevo bisogno per un progetto che richiedeva un AutorizeAttribute e non funzionava con l'interfaccia (penso che fosse a causa di SignalR, ma non ricordo più). In ogni caso, per favore, considera l'altra risposta che ho dato in questo stesso post, poiché è più sicuro. –

+0

Questo non funzionerà per le richieste anonime. –

1

Dopo averci pensato un po 'di più, è una cattiva idea mescolare i cookie e i token modulo in quanto sconfigge l'intero scopo del token anti-contraffazione. È meglio mantenere la parte cookie come cookie mentre si sposta la parte del modulo in un'intestazione auth, quindi questa nuova risposta (sempre come 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; 
     } 
    } 
    } 

Poi basta decorare il vostro controller o metodi con [ApiValidateAntiForgeryToken]

e aggiungere il file rasoio questo per generare token per 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>