2016-04-14 71 views
9

我一直在尝试重新创建一个Ajax版本的ValidateAntiForgeryToken - 有很多关于如何为MVC的先前版本做到这一点的博客文章,但与最新的MVC 6,没有任何代码是相关的。然而,我所追求的核心原则是验证看看Cookie和页眉的__RequestVerificationToken,而不是将Cookie与表单值进行比较。我使用的是MVC 6.0.0-rc1-final,dnx451框架,所有的Microsoft.Extensions库都是1.0.0-rc1-final。ValidateAntiForgeryToken Ajax请求与AspNet核心MVC

我最初的想法是继承ValidateAntiForgeryTokenAttribute,但在查看源代码时,我需要返回自己的授权过滤器实现以使其能够查看标题。

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] 
public class ValidateAjaxAntiForgeryTokenAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter 
{ 
    public int Order { get; set; } 
    public bool IsReusable => true; 
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) 
    { 
     return serviceProvider.GetRequiredService<ValidateAjaxAntiforgeryTokenAuthorizationFilter>(); 
    } 
} 

这样,然后我做了我自己的ValidateAntiforgeryTokenAuthorizationFilter

public class ValidateAjaxAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy 
{ 
    private readonly IAntiforgery _antiforgery; 
    private readonly ILogger _logger; 
    public ValidateAjaxAntiforgeryTokenAuthorizationFilter(IAntiforgery antiforgery, ILoggerFactory loggerFactory) 
    { 
     if (antiforgery == null) 
     { 
      throw new ArgumentNullException(nameof(antiforgery)); 
     } 
     _antiforgery = antiforgery; 
     _logger = loggerFactory.CreateLogger<ValidateAjaxAntiforgeryTokenAuthorizationFilter>(); 
    } 
    public async Task OnAuthorizationAsync(AuthorizationContext context) 
    { 
     if (context == null) 
     { 
      throw new ArgumentNullException(nameof(context)); 
     } 
     if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context)) 
     { 
      try 
      { 
       await _antiforgery.ValidateRequestAsync(context.HttpContext); 
      } 
      catch (AjaxAntiforgeryValidationException exception) 
      { 
       _logger.LogInformation(1, string.Concat("Ajax Antiforgery token validation failed. ", exception.Message)); 
       context.Result = new BadRequestResult(); 
      } 
     } 
    } 
    protected virtual bool ShouldValidate(AuthorizationContext context) 
    { 
     if (context == null) 
     { 
      throw new ArgumentNullException(nameof(context)); 
     } 
     return true; 
    } 
    private bool IsClosestAntiforgeryPolicy(IList<IFilterMetadata> filters) 
    { 
     // Determine if this instance is the 'effective' antiforgery policy. 
     for (var i = filters.Count - 1; i >= 0; i--) 
     { 
      var filter = filters[i]; 
      if (filter is IAntiforgeryPolicy) 
      { 
       return object.ReferenceEquals(this, filter); 
      } 
     } 
     Debug.Fail("The current instance should be in the list of filters."); 
     return false; 
    } 
} 

但版本,我找不到合适的NuGet包和命名空间包含IAntiforgeryPolicy。当我在GitHub上找到接口时 - 我能找到哪个包?

我的下一次尝试是改为在IAntiforgery注射后,并用我自己的AjaxAntiforgery替换DefaultAntiforgery

public class AjaxAntiforgery : DefaultAntiforgery 
{ 
    private readonly AntiforgeryOptions _options; 
    private readonly IAntiforgeryTokenGenerator _tokenGenerator; 
    private readonly IAntiforgeryTokenSerializer _tokenSerializer; 
    private readonly IAntiforgeryTokenStore _tokenStore; 
    private readonly ILogger<AjaxAntiforgery> _logger; 
    public AjaxAntiforgery(
     IOptions<AntiforgeryOptions> antiforgeryOptionsAccessor, 
     IAntiforgeryTokenGenerator tokenGenerator, 
     IAntiforgeryTokenSerializer tokenSerializer, 
     IAntiforgeryTokenStore tokenStore, 
     ILoggerFactory loggerFactory) 
    { 
     _options = antiforgeryOptionsAccessor.Value; 
     _tokenGenerator = tokenGenerator; 
     _tokenSerializer = tokenSerializer; 
     _tokenStore = tokenStore; 
     _logger = loggerFactory.CreateLogger<AjaxAntiforgery>(); 
    } 
} 

我走到这一步之前,我陷入停滞,因为没有对ILoggerFactoryCreateLogger<T>()没有通用的方法。 DefaultAntiforgery的源代码有Microsoft.Extensions.Options,但我无法在任何Nuget包中找到该名称空间。 Microsoft.Extensions.OptionsModel存在,但只是带来了IOptions<out TOptions>界面。为了全面了解这一点,一旦我得到了授权过滤器的工作,或者我得到了一个新的实现IAntiforgery,我在哪里或如何注册依赖注入来使用它 - 并且仅限于操作我会接受Ajax请求?

回答

3

我一直在类似的情况下摔角,将角度POST与MVC6接口,并提出以下建议。

需要解决两个问题:将安全令牌放入MVC的防伪验证子系统中,并将角度JSON格式的回发数据转换为MVC模型。

我通过在Startup.Configure()中插入的一些自定义中间件来处理第一步。中间件类是非常简单的:

public static class UseAngularXSRFExtension 
{ 
    public const string XSRFFieldName = "X-XSRF-TOKEN"; 

    public static IApplicationBuilder UseAngularXSRF(this IApplicationBuilder builder) 
    { 
     return builder.Use(next => context => 
     { 
      switch(context.Request.Method.ToLower()) 
      { 
       case "post": 
       case "put": 
       case "delete": 
        if(context.Request.Headers.ContainsKey(XSRFFieldName)) 
        { 
         var formFields = new Dictionary<string, StringValues>() 
         { 
          { XSRFFieldName, context.Request.Headers[XSRFFieldName] } 
         }; 

         // this assumes that any POST, PUT or DELETE having a header 
         // which includes XSRFFieldName is coming from angular, so 
         // overwriting context.Request.Form is okay (since it's not 
         // being parsed by MVC's internals anyway) 
         context.Request.Form = new FormCollection(formFields); 
        } 

        break; 
      } 

      return next(context); 
     }); 
    } 
} 

您与Startup.Configure()方法中的以下行插入到这个管道:

app.UseAngularXSRF(); 

我做了调用应用程序之前,这一权利。 UseMVC()。

请注意,这个扩展在它存在的任何POST,PUT或DELETE上传输XSRF头,并且它通过覆盖现有的表单字段集合来实现。这符合我的设计模式 - XSRF头文件在请求中的唯一时间是来自我写的一些角码,但它可能不适合你。

我也认为你需要配置antiforgery子系统使用正确的名称为XSRF字段名称(我不知道默认是什么)。您可以通过插入以下行Startup.ConfigureServices(这样做):

services.ConfigureAntiforgery(options => options.FormFieldName = UseAngularXSRFExtension.XSRFFieldName); 

我插入的行services.AddAntiforgery()之前,这一权利。

将XSRF令牌放入请求流有几种方法。我要做的就是添加如下的观点:

...top of view... 
@inject Microsoft.AspNet.Antiforgery.IAntiforgery af 
...rest of view... 

...inside the angular function... 
      var postHeaders = { 
       'X-XSRF-TOKEN': '@(af.GetTokens(this.Context).FormToken)', 
       'Content-Type': 'application/json; charset=utf-8', 
      }; 

      $http.post('/Dataset/DeleteDataset', JSON.stringify({ 'siteID': siteID }), 
       { 
        headers: postHeaders, 
       }) 
...rest of view... 

第二部分 - 翻译JSON数据 - 通过与[FromBody]装饰你的操作方法,模型类处理:

 // the [FromBody] attribute on the model -- and a class model, rather than a 
     // single integer model -- are necessary so that MVC can parse the JSON-formatted 
     // text POSTed by angular 
     [HttpPost] 
     [ValidateAntiForgeryToken] 
     public IActionResult DeleteDataset([FromBody] DeleteSiteViewModel model) 
     { 
} 

[FromBody]仅适用于类实例。即使在我的情况下,我感兴趣的只是一个整数,但我仍然需要虚拟一个类,它只包含一个整数属性。

希望这会有所帮助。

0

在Ajax调用中使用防伪标记是可能的,但如果您尝试保护Api,我真的会建议使用Access Token。

如果您依赖存储在cookie中的身份令牌作为Api的身份验证,则需要编写代码以补偿cookie验证超时的时间,并且您的Ajax帖子将重定向到登录屏幕。这对于SPA和Angular应用程序尤其重要。

改为使用Access Token实现,将允许您刷新访问令牌(使用刷新令牌),进行长时间运行的会话并阻止cookie窃贼访问您的Apis ..并且它也会停止XSRF :)

访问令牌的目的是保护资源,如Web Apis。

+1

你有代码示例吗? – Grandizer

8

我有类似的问题。我不知道在.NET中是否有任何关于此的更改,但是,当时,我在配置服务方法Startup.cs之前添加了以下行,之前行services.AddMvc(),以便验证通过Ajax发送AntiForgeryToken:

services.AddAntiforgery(options => 
{ 
    options.CookieName = "yourChosenCookieName"; 
    options.HeaderName = "RequestVerificationToken"; 
}); 

AJAX调用会像下面这样:

var token = $('input[type=hidden][name=__RequestVerificationToken]', document).val(); 

var request = $.ajax({ 
    data: { 'yourField': 'yourValue' }, 
    ... 
    headers: { 'RequestVerificationToken': token } 
}); 

然后,只需使用本地属性[ValidadeAntiForgeryToken]在你的操作。

+0

谢谢。使用.Net Core 2 Razor页面 - 非常完美 – Gfw