实现对Web API ValidatingAntiForgeryToken属性与MVC 4 RC问题

2019-06-17 14:40发布

我在做基于JSON的AJAX请求,并与MVC控制器一直很感激菲尔哈克他与AJAX预防CSRF和,约翰·德里森的更新的防XSRF为MVC 4 RC 。 但是,正如我过渡API为核心控制器的Web API,我打的问题,其中的两种方法之间的功能是明显不同的,我无法过渡CSRF代码。

斯科特提出了类似的问题,最近这是回答由达林季米特洛夫。 Darin的解决方案涉及实施授权过滤器调用AntiForgery.Validate。 不幸的是,这个代码不为我工作(见下文)和 - 诚实 - 对我来说太先进。

据我了解,菲尔的解决方案克服使得在没有表单元素的JSON请求时,MVC防伪问题; 表单元素假定/由AntiForgery.Validate方法预期。 我认为 ,这可能是为什么我在与达林的解决的问题了。 我收到HttpAntiForgeryException“所需的防伪造表单字段‘__RequestVerificationToken’不存在”。 我确信,该​​令牌被张贴(尽管每菲尔哈克的解决方案的标题)。 这里的客户端调用的快照:

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

我尝试了黑客通过一起捣碎约翰与达林的解决方案,并能得到的东西的工作,但我引入HttpContext.Current,这个不确定是否适当/安全的,为什么我不能使用提供HttpActionContext。

这是我的不雅混搭..变化是2号线在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();
}

我的问题是:

  • 我在想,达林的方案假设一个表单元素的存在是否正确?
  • 什么是混搭了约翰的MVC 4 RC码达林的Web API过滤器优雅的方式?

提前致谢!

Answer 1:

你可以尝试从标题写着:

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

注意: GetCookies是存在于类的扩展方法HttpRequestHeadersExtensions其是部分System.Net.Http.Formatting.dll 。 它将最有可能存在C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET MVC 4\Assemblies\System.Net.Http.Formatting.dll



Answer 2:

只是想补充一点,这种方法为我工作也(阿贾克斯JSON发布到Web API端点),虽然我从ActionFilterAttribute继承和重写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.");
        }
    }
}


Answer 3:

使用Darin的答案,与头的存在的核对扩展方法。 支票是指所产生的错误信息是更表明了什么问题(“所需的防伪型窗体域‘__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();
}


Answer 4:

使用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>


Answer 5:

如果有帮助的人,在.NET核心,标头的默认值实际上只是“RequestVerificationToken”,没有“__”。 所以,如果你改变标题的关键是,相反,它会工作。

您也可以覆盖头名称,如果你喜欢:

services.AddAntiforgery(o => o.HeaderName = "__RequestVerificationToken")



文章来源: Problems implementing ValidatingAntiForgeryToken attribute for Web API with MVC 4 RC