2012-07-30 32 views
19

Estoy haciendo solicitudes AJAX basadas en JSON y, con los controladores MVC, le he agradecido a Phil Haack por su Preventing CSRF with AJAX y, a Johan Driessen Updated Anti-XSRF for MVC 4 RC. Pero, al hacer la transición de los controladores centrados en la API a la API web, estoy abordando problemas donde la funcionalidad entre los dos enfoques es marcadamente diferente y no puedo hacer la transición del código CSRF.Problemas al implementar el atributo ValidatingAntiForgeryToken para la API web con MVC 4 RC

ScottS levantó un similar question recientemente que fue answered por Darin Dimitrov. La solución de Darin implica la implementación de un filtro de autorización que llama a AntiForgery.Validate. Desafortunadamente, este código no funciona para mí (ver el párrafo siguiente) y, honestamente, es demasiado avanzado para mí.

Según tengo entendido, la solución de Phil supera el problema con MVC AntiForgery al hacer peticiones JSON en la ausencia de un elemento de formulario; el elemento de forma es asumido/esperado por el método AntiForgery.Validate. I creo que esta puede ser la razón por la que estoy teniendo problemas con la solución de Darin también. Recibo una HttpAntiForgeryException "El campo de formulario antifalsificación requerido '__RequestVerificationToken' no está presente". Estoy seguro de que el token se está enviando por correo (aunque en el encabezado según la solución de Phil Haack). He aquí una instantánea de la llamada de cliente:

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

Probé un corte por maceración junto solución de Johan con Darin y fue capaz de hacer las cosas de trabajo, pero estoy presentando HttpContext.Current, sin saber si esto es apropiado/seguro y por qué no puede usar el proporcionado HttpActionContext.

Aquí es mi poco elegante mash-up .. el cambio es las 2 líneas en el bloque try:

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

Mis preguntas son:

  • Estoy en lo correcto al pensar que la solución de Darin asume la existencia de un elemento de forma?
  • ¿Cuál es una manera elegante de mash-up de filtro Web API de Darin con MVC 4 código RC de Johan?

¡Gracias de antemano!

+0

¿cuál es el resultado de? –

Respuesta

31

Usted podría intentar la lectura de las cabeceras:

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

Nota: GetCookies es un método de extensión que existe en la clase HttpRequestHeadersExtensions que es parte de System.Net.Http.Formatting.dll. Es más probable existirá en C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll

+1

Oye ... ¿Qué pasa con el método 'GetCookies()'? ¿Cómo tenemos que implementarlo en general? –

+1

@Metropolitan ver nota de actualización en la respuesta – Simon

+0

De [código fuente MVC] (http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.WebPages/Helpers/AntiXsrf/AntiForgeryTokenStore.cs), he visto que están utilizando 'httpContext.Request.Form [_config.FormFieldName]' en el método GetFormToken. ¿Hay alguna razón para no usar Request.Params para acceder a los valores de formulario y encabezado? Creo que también puede incluir cookies :(. Sin embargo, pueden verificar el encabezado de la solicitud cuando el Formulario no está disponible ... –

12

sólo quería añadir que este enfoque funcionó para mí también (.ajax publicar JSON a un punto final de Web API), aunque he simplificado un poco heredando de ActionFilterAttribute y reemplazando el método OnActionExecuting.

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

¿Por qué el voto a favor? No soy el que responde, pero los votos a favor merecen un comentario. – DarrellNorton

0

Método de extensión utilizando la respuesta de Darin, con una verificación de la presencia del encabezado. La comprobación significa que el mensaje de error resultante es más indicativo de lo que está mal ("El campo de formulario antifalsificación requerido" __RequestVerificationToken "no está presente.") Versus "No se encontró el encabezado dado."Uso

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

Una implementación usando 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; 
     } 
    } 
    } 

A continuación, sólo la decoración de su controlador o métodos con [ApiValidateAntiForgeryToken]

y añadir a la máquina de afeitar archive esto para generar su token para 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> 
Cuestiones relacionadas