Руководство по безопасности для веб-API ASP.NET 2 OData

по Майк Уоссон

В этом разделе описываются некоторые проблемы безопасности, которые следует учитывать при предоставлении набора данных через OData для веб-API ASP.NET 2 на ASP.NET 4. x.

Безопасность EDM

Семантика запросов основана на модели EDM, а не на базовых типах моделей. Вы можете исключить свойство из модели EDM, и оно не будет видимо для запроса. Например, предположим, что модель включает тип сотрудника со свойством оклада. Может потребоваться исключить это свойство из EDM, чтобы скрыть его от клиентов.

Существует два способа исключить свойство из EDM. Вы можете задать атрибут [игноредатамембер] для свойства в классе Model:

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

Также можно удалить свойство из модели EDM программным способом:

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

Безопасность запросов

Вредоносный или упрощенный клиент может создать запрос, выполнение которого занимает очень много времени. В худшем случае это может нарушить доступ к службе.

Атрибут [с поддержкой запроса] — это фильтр действий, который анализирует, проверяет и применяет запрос. Фильтр преобразует параметры запроса в выражение LINQ. Когда контроллер OData возвращает тип IQueryable , поставщик IQueryable LINQ преобразует выражение LINQ в запрос. Таким образом, производительность зависит от используемого поставщика LINQ, а также от конкретных характеристик набора данных или схемы базы данных.

Дополнительные сведения об использовании параметров запросов OData в веб-API ASP.NET см. в разделе Поддержка параметров запросов OData.

Если известно, что все клиенты являются доверенными (например, в корпоративной среде) или если набор данных является небольшим, производительность запросов может быть не проблемой. В противном случае следует принять во внимание следующие рекомендации.

  • Протестируйте службу с различными запросами и проверяйте базу данных.

  • Включите серверную подкачку, чтобы избежать возвращения большого набора данных в одном запросе. Дополнительные сведения см. в разделе подкачка, управляемая сервером.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Требуется $filter и $orderby? Некоторые приложения могут разрешать подкачку клиента с помощью $top и $skip, но отключать другие параметры запроса.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Рекомендуется ограничивать $orderby свойствами в кластеризованном индексе. Сортировка больших данных без кластеризованного индекса выполняется слишком долго.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Максимальное число узлов: свойство макснодекаунт в [запрашиваемое] задает максимальное число узлов, допустимых в дереве синтаксиса $Filter. Значение по умолчанию — 100, но может потребоваться задать меньшее значение, так как большое количество узлов может замедляться для компиляции. Это особенно справедливо при использовании LINQ to Objects (т. е. запросов LINQ к коллекции в памяти без использования промежуточного поставщика LINQ).

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • Рассмотрите возможность отключения функций Any () и All (), так как они могут быть слишком длительными.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Если какие-либо строковые свойства—содержат большие строки, например, описание продукта или запись—в блоге, попробуйте отключить строковые функции.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Рассмотрите возможность запрета фильтрации по свойствам навигации. Фильтрация по свойствам навигации может привести к созданию объединения, которое может быть заработано в зависимости от схемы базы данных. В следующем коде показан проверяющий элемент управления, который предотвращает фильтрацию свойств навигации. Дополнительные сведения о средствах проверки запросов см. в разделе Проверка запросов.

    // 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");
        }
    }
    
  • Рассмотрите возможность ограничивать $filter запросы, написав проверяющий элемент управления, настроенный для базы данных. Например, рассмотрим следующие два запроса:

    • Все фильмы с субъектами, фамилии которых начинаются с "A".

    • Все фильмы, выпущенные в 1994.

      Если фильмы не индексируются субъектами, для первого запроса может потребоваться, чтобы ядро СУБД проверяло весь список фильмов. В то время как второй запрос может быть приемлемым, при условии, что фильмы индексируются по году выпуска.

      В следующем коде показан проверяющий элемент управления, позволяющий фильтровать свойства "Релеасэйеар" и "Title", но не другие свойства.

      // 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);
          }
      }
      
  • Как правило, определите, какие функции $filter вам нужны. Если клиентам не требуется полное выразительность $filter, можно ограничить разрешенные функции.