2016-04-14 135 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