2016-04-14 11 views
9

Ich habe versucht, eine Ajax-Version des ValidateAntiForgeryToken neu zu erstellen - es gibt viele Blog-Posts, wie dies für frühere Versionen von MVC, aber mit der neuesten MVC 6, keine der Code ist relevant. Das Kernprinzip, dem ich nachgehe, ist jedoch, dass die Validierung den Cookie und den Header für die __RequestVerificationToken betrachtet, anstatt den Cookie mit einem Formularwert zu vergleichen. Ich verwende MVC 6.0.0-rc1-final, dnx451-Framework, und alle Microsoft.Extensions-Bibliotheken sind 1.0.0-rc1-final.ValidateAntiForgeryToken in Ajax Anfrage mit AspNet Core MVC

Mein erster Gedanke war, nur zu erben ValidateAntiForgeryTokenAttribute, aber mit Blick auf den Quellcode, würde ich meine eigene Implementierung eines Authorization Filter zurückgeben müssen, um es auf den Header zu sehen.

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

Als solche habe ich dann meine eigene Version von 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; 
    } 
} 

Allerdings habe ich nicht die richtige Nuget Paket und Namespace, IAntiforgeryPolicy enthält finden können. Während ich das Interface auf GitHub gefunden habe - in welchem ​​Paket finde ich es?

Mein nächster Versuch war, stattdessen nach der IAntiforgery Injektion zu gehen, und ersetzen Sie die DefaultAntiforgery durch meine eigene AjaxAntiforgery.

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

bekam ich so weit, bevor ich heraus ins Stocken geraten, weil es keine generische Methode auf ILoggerFactory für CreateLogger<T>() ist. Der Quellcode für DefaultAntiforgery hat Microsoft.Extensions.Options, aber ich kann diesen Namespace in keinem Nuget-Paket finden. Microsoft.Extensions.OptionsModel existiert, aber das bringt nur die IOptions<out TOptions> Schnittstelle mit.

Um all dies zu folgen, sobald ich den Autorisierungsfilter zu arbeiten, oder ich bekomme eine neue Implementierung von IAntiforgery, wo oder wie registriere ich es mit der Abhängigkeitsinjektion, um es zu verwenden - und nur für die Aktionen dass ich Ajax-Anfragen annehmen werde?

Antwort

3

Ich habe mit einer ähnlichen Situation gerungen, eckige POSTs mit MVC6 verbunden und kam auf das folgende.

Es gibt zwei Probleme, die behoben werden müssen: Abrufen des Sicherheitstokens in MVCs Antivirus-Validierungssubsystem und Konvertieren der JSON-formatierten Postback-Daten von angular in ein MVC-Modell.

Ich handle den ersten Schritt über einige benutzerdefinierte Middleware in Startup.Configure() eingefügt. Die Middleware-Klasse ist ziemlich einfach:

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

du in der Startup.Configure() -Methode mit der folgenden Zeile in die Pipeline einzufügen:

app.UseAngularXSRF(); 

Ich habe dieses Recht vor dem Aufruf von App. Verwenden SieMVC().

Beachten Sie, dass diese Erweiterung den XSRF-Header auf jedem POST, PUT oder DELETE, wo es existiert, überträgt, und zwar durch Überschreiben der vorhandenen Formularfeldsammlung.Das passt zu meinem Design-Muster - das einzige Mal, dass der XSRF-Header in einer Anfrage enthalten ist, ist, dass es aus irgendeinem Winkelcode stammt, den ich geschrieben habe - aber es passt vielleicht nicht zu dir.

Ich denke auch, dass Sie das Antiforgery-Subsystem konfigurieren müssen, um den richtigen Namen für den XSRF-Feldnamen zu verwenden (ich bin mir nicht sicher, was der Standardwert ist). Sie können dies tun, indem Sie die folgende Zeile in Startup.ConfigureServices Einfügen():

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

ich dieses Recht vor der Zeile services.AddAntiforgery() eingefügt.

Es gibt mehrere Möglichkeiten, das XSRF-Token in den Anforderungsstream zu bekommen. Was ich tue, ist folgendes zu der Ansicht hinzufügen:

...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... 

Der zweite Teil - die JSON-Daten zu übersetzen - wird durch die Dekoration der Modellklasse auf Ihrer Aktion-Methode behandelt mit [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] funktioniert nur für Klasseninstanzen. Obwohl ich in meinem Fall nur eine ganze Zahl interessiert, musste ich immer noch eine Klasse täuschen, die nur eine ganzzahlige Eigenschaft enthält.

Hoffe, das hilft.

0

Die Verwendung eines Anti-Fälschungs-Tokens in einem Ajax-Aufruf ist möglich, aber wenn Sie versuchen, eine Api zu sichern, würde ich stattdessen die Verwendung eines Access Tokens empfehlen.

Wenn Sie sich auf ein in einem Cookie gespeichertes Identitäts-Token als Authentifizierung für Ihre API verlassen, müssen Sie Code schreiben, um zu kompensieren, wenn Ihre Cookie-Authentifizierung abläuft und Ihr Ajax-Post auf einen Anmeldebildschirm umgeleitet wird. Dies ist besonders wichtig für SPAs und Angular Apps.

Mit einer Access Token-Implementierung können Sie Ihr Zugriffstoken aktualisieren (mit einem Aktualisierungstoken), lange Sitzungen ausführen und verhindern, dass Cookie-Diebe auf Ihre Apis zugreifen. Außerdem wird XSRF beendet :)

Ein Access-Token Zweck ist die Sicherung von Ressourcen, wie Web-API.

+1

würden Sie ein Codebeispiel haben? – Grandizer

8

Ich hatte ähnliches Problem. Ich weiß nicht, ob irgendwelche Änderungen diesbezüglich in .NET kommen, aber zu dem Zeitpunkt, fügte ich die folgenden Zeilen zu ConfigureServices Methode in Startup.cs, vor der Zeile services.AddMvc(), um Validierung der AntiForgeryToken über Ajax gesendet:

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

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

Dann einfach das native Attribut [ValidadeAntiForgeryToken] in Ihren Aktionen:

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

der Ajax-Aufruf in etwa wie folgt sein würde.

+0

Danke. Verwenden von .Net Core 2 Razor-Seiten - funktionierte perfekt – Gfw