Je lance des requêtes basées sur JSON AJAX et, avec les contrôleurs MVC, je suis très reconnaissant à Phil Haack pour son Preventing CSRF avec AJAX et de Johan Driessen _. Anti-XSRF mis à jour pour MVC 4 RC }. Toutefois, lorsque je passe des contrôleurs centrés sur des API à des API Web, je rencontre des problèmes pour lesquels la fonctionnalité entre les deux approches est très différente et je ne parviens pas à effectuer la transition du code CSRF.
ScottS a récemment soulevé une question similaire qui était répondue par Darin Dimitrov. La solution de Darin implique la mise en œuvre d'un filtre d'autorisation qui appelle AntiForgery.Validate. Malheureusement, ce code ne fonctionne pas pour moi (voir paragraphe suivant) et - honnêtement - est trop avancé pour moi.
Si j'ai bien compris, la solution de Phil résout le problème posé par MVC AntiForgery lors de la création de requêtes JSON en l'absence d'un élément de formulaire. l'élément de formulaire est supposé/attendu par la méthode AntiForgery.Validate. Je crois que c'est peut-être pour ça que j'ai aussi des problèmes avec la solution de Darin. Je reçois une exception HttpAntiForgeryException "Le champ de formulaire anti-falsification requis '__RequestVerificationToken' n'est pas présent". Je suis certain que le jeton est en train d'être posté (bien que l'en-tête corresponde à la solution de Phil Haack). Voici un instantané de l'appel du client:
$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) {
...
});
J'ai essayé de bidouiller en mélangeant la solution de Johan avec celle de Darin et j'ai réussi à faire fonctionner les choses, mais je présente HttpContext.Current, ne sachant pas si cela est approprié/sécurisé et pourquoi je ne peux pas utiliser le HttpActionContext fourni.
Voici mon inélégant mash-up .. le changement est les 2 lignes dans le bloc 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();
}
Mes questions sont:
Merci d'avance!
Vous pouvez essayer de lire les en-têtes:
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);
Remarque: GetCookies
est une méthode d'extension qui existe dans la classe HttpRequestHeadersExtensions
et qui fait partie de System.Net.Http.Formatting.dll
. Il existera très probablement dans C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll
Je voulais juste ajouter que cette approche fonctionnait pour moi également (publication de .Jax en JSON sur un noeud final Web API), bien que je l’aie simplifiée un peu en héritant de ActionFilterAttribute et en redéfinissant la méthode 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.");
}
}
}
Si cela aide quelqu'un, dans le noyau .net, la valeur par défaut de l'en-tête est en réalité simplement "RequestVerificationToken", sans le "__". Donc, si vous modifiez la clé de l'en-tête en cela, cela fonctionnera.
Vous pouvez également remplacer le nom de l'en-tête si vous aimez:
services.AddAntiforgery(o => o.HeaderName = "__RequestVerificationToken")
Méthode d'extension utilisant la réponse de Darin, avec vérification de la présence de l'en-tête. La vérification signifie que le message d'erreur résultant est plus révélateur de ce qui ne va pas ("Le champ de formulaire anti-falsification requis" __RequestVerificationToken "n'est pas présent.") Par rapport à "L'en-tête donné n'a pas été trouvé".
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;
}
Utilisation d'ApiController:
public IHttpActionResult Get()
{
if (Request.IsHeaderAntiForgeryTokenValid())
return Ok();
else
return BadRequest();
}
Une implémentation utilisant 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;
}
}
}
Ensuite, décorez simplement votre contrôleur ou vos méthodes avec [ApiValidateAntiForgeryToken]
Et ajoutez ceci au fichier rasoir pour générer votre jeton pour 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>