Wskazówki dotyczące zabezpieczeń ASP.NET internetowego interfejsu API 2 OData

Autor: Mike Wasson

W tym temacie opisano niektóre problemy z zabezpieczeniami, które należy wziąć pod uwagę podczas uwidaczniania zestawu danych za pomocą usługi OData dla ASP.NET internetowego interfejsu API 2 w ASP.NET 4.x.

Zabezpieczenia EDM

Semantyka zapytań jest oparta na modelu danych jednostki (EDM), a nie na podstawowych typach modelu. Możesz wykluczyć właściwość z EDM i nie będzie widoczna dla zapytania. Załóżmy na przykład, że model zawiera typ pracownika z właściwością Salary. Możesz wykluczyć tę właściwość z EDM, aby ukryć ją przed klientami.

Istnieją dwa sposoby wykluczania właściwości z EDM. Atrybut [IgnoreDataMember] można ustawić we właściwości w klasie modelu:

public class Employee
{
    public string Name { get; set; }
    public string Title { get; set; }
    [IgnoreDataMember]
    public decimal Salary { get; set; } // Not visible in the EDM
}

Właściwość można również usunąć z programu EDM:

var employees = modelBuilder.EntitySet<Employee>("Employees");
employees.EntityType.Ignore(emp => emp.Salary);

Zabezpieczenia zapytań

Złośliwy lub naiwny klient może być w stanie utworzyć zapytanie, które trwa bardzo długo. W najgorszym przypadku może to zakłócić dostęp do usługi.

Atrybut [Queryable] to filtr akcji, który analizuje, weryfikuje i stosuje zapytanie. Filtr konwertuje opcje zapytania na wyrażenie LINQ. Gdy kontroler OData zwraca typ IQueryable , dostawca IQueryable LINQ konwertuje wyrażenie LINQ na zapytanie. W związku z tym wydajność zależy od używanego dostawcy LINQ, a także od określonych cech schematu zestawu danych lub bazy danych.

Aby uzyskać więcej informacji na temat używania opcji zapytań OData w interfejsie API sieci Web ASP.NET, zobacz Obsługa opcji zapytania OData.

Jeśli wiesz, że wszyscy klienci są zaufani (na przykład w środowisku przedsiębiorstwa) lub jeśli zestaw danych jest mały, wydajność zapytań może nie być problemem. W przeciwnym razie należy wziąć pod uwagę następujące zalecenia.

  • Przetestuj usługę przy użyciu różnych zapytań i profilowania bazy danych.

  • Włącz stronicowanie oparte na serwerze, aby uniknąć zwracania dużego zestawu danych w jednym zapytaniu. Aby uzyskać więcej informacji, zobacz stronicowanie oparte na serwerze.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Czy potrzebujesz $filter i $orderby? Niektóre aplikacje mogą zezwalać na stronicowanie klientów przy użyciu $top i $skip, ale wyłączyć inne opcje zapytania.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Rozważ ograniczenie $orderby do właściwości w indeksie klastrowanym. Sortowanie dużych danych bez indeksu klastrowanego działa wolno.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Maksymalna liczba węzłów: właściwość MaxNodeCount na [ Queryable] ustawia maksymalną liczbę węzłów dozwolonych w drzewie składni $filter. Wartość domyślna to 100, ale możesz ustawić niższą wartość, ponieważ duża liczba węzłów może być niska do skompilowania. Jest to szczególnie istotne, jeśli używasz LINQ to Objects (tj. zapytań LINQ dotyczących kolekcji w pamięci, bez użycia pośredniego dostawcy LINQ).

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • Rozważ wyłączenie funkcji any() i all(), ponieważ mogą one być powolne.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Jeśli jakiekolwiek właściwości ciągu zawierają duże ciągi , na przykład opis produktu lub wpis w blogu, rozważ wyłączenie funkcji ciągów.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Rozważ uniemożliwienie filtrowania we właściwościach nawigacji. Filtrowanie właściwości nawigacji może spowodować sprzężenie, co może być powolne w zależności od schematu bazy danych. Poniższy kod przedstawia moduł sprawdzania poprawności zapytań, który uniemożliwia filtrowanie we właściwościach nawigacji. Aby uzyskać więcej informacji na temat modułów sprawdzania poprawności zapytań, zobacz Sprawdzanie poprawności zapytań.

    // Validator to prevent filtering on navigation properties.
    public class MyFilterQueryValidator : FilterQueryValidator
    {
        public override void ValidateNavigationPropertyNode(
            Microsoft.Data.OData.Query.SemanticAst.QueryNode sourceNode, 
            Microsoft.Data.Edm.IEdmNavigationProperty navigationProperty, 
            ODataValidationSettings settings)
        {
            throw new ODataException("No navigation properties");
        }
    }
    
  • Rozważ ograniczenie zapytań $filter przez napisanie modułu sprawdzania poprawności dostosowanego do bazy danych. Rozważmy na przykład następujące dwa zapytania:

    • Wszystkie filmy z aktorami, których nazwisko zaczyna się od "A".

    • Wszystkie filmy wydane w 1994 roku.

      Jeśli filmy nie są indeksowane przez aktorów, pierwsze zapytanie może wymagać od aparatu bazy danych skanowania całej listy filmów. Drugie zapytanie może być dopuszczalne, przy założeniu, że filmy są indeksowane według roku wydania.

      Poniższy kod przedstawia moduł sprawdzania poprawności, który umożliwia filtrowanie we właściwościach "ReleaseYear" i "Title", ale nie ma innych właściwości.

      // Validator to restrict which properties can be used in $filter expressions.
      public class MyFilterQueryValidator : FilterQueryValidator
      {
          static readonly string[] allowedProperties = { "ReleaseYear", "Title" };
      
          public override void ValidateSingleValuePropertyAccessNode(
              SingleValuePropertyAccessNode propertyAccessNode,
              ODataValidationSettings settings)
          {
              string propertyName = null;
              if (propertyAccessNode != null)
              {
                  propertyName = propertyAccessNode.Property.Name;
              }
      
              if (propertyName != null && !allowedProperties.Contains(propertyName))
              {
                  throw new ODataException(
                      String.Format("Filter on {0} not allowed", propertyName));
              }
              base.ValidateSingleValuePropertyAccessNode(propertyAccessNode, settings);
          }
      }
      
  • Ogólnie rzecz biorąc, należy wziąć pod uwagę, które funkcje $filter są potrzebne. Jeśli klienci nie potrzebują pełnej ekspresywności $filter, możesz ograniczyć dozwolone funkcje.