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

по Майк Уоссонby Mike Wasson

В этом разделе описываются некоторые проблемы безопасности, которые следует учитывать при предоставлении набора данных через OData для веб-API ASP.NET 2 на ASP.NET 4. x.This topic describes some of the security issues that you should consider when exposing a dataset through OData for ASP.NET Web API 2 on ASP.NET 4.x.

Безопасность EDMEDM Security

Семантика запросов основана на модели EDM, а не на базовых типах моделей.The query semantics are based on the entity data model (EDM), not the underlying model types. Вы можете исключить свойство из модели EDM, и оно не будет видимо для запроса.You can exclude a property from the EDM and it will not be visible to the query. Например, предположим, что модель включает тип сотрудника со свойством оклада.For example, suppose your model includes an Employee type with a Salary property. Может потребоваться исключить это свойство из EDM, чтобы скрыть его от клиентов.You might want to exclude this property from the EDM to hide it from clients.

Существует два способа исключить свойство из EDM.There are two ways to exclude a property from the EDM. Вы можете задать атрибут [игноредатамембер] для свойства в классе Model:You can set the [IgnoreDataMember] attribute on the property in the model class:

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 программным способом:You can also remove the property from the EDM programmatically:

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

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

Вредоносный или упрощенный клиент может создать запрос, выполнение которого занимает очень много времени.A malicious or naive client may be able to construct a query that takes a very long time to execute. В худшем случае это может нарушить доступ к службе.In the worst case this can disrupt access to your service.

Атрибут [с поддержкой запроса] — это фильтр действий, который анализирует, проверяет и применяет запрос.The [Queryable] attribute is an action filter that parses, validates, and applies the query. Фильтр преобразует параметры запроса в выражение LINQ.The filter converts the query options into a LINQ expression. Когда контроллер OData возвращает тип IQueryable , поставщик IQueryable LINQ преобразует выражение LINQ в запрос.When the OData controller returns an IQueryable type, the IQueryable LINQ provider converts the LINQ expression into a query. Таким образом, производительность зависит от используемого поставщика LINQ, а также от конкретных характеристик набора данных или схемы базы данных.Therefore, performance depends on the LINQ provider that is used, and also on the particular characteristics of your dataset or database schema.

Дополнительные сведения об использовании параметров запросов OData в веб-API ASP.NET см. в разделе Поддержка параметров запросов OData.For more information about using OData query options in ASP.NET Web API, see Supporting OData Query Options.

Если известно, что все клиенты являются доверенными (например, в корпоративной среде) или если набор данных является небольшим, производительность запросов может быть не проблемой.If you know that all clients are trusted (for example, in an enterprise environment), or if your dataset is small, query performance might not be an issue. В противном случае следует принять во внимание следующие рекомендации.Otherwise, you should consider the following recommendations.

  • Протестируйте службу с различными запросами и проверяйте базу данных.Test your service with various queries and profile the DB.

  • Включите серверную подкачку, чтобы избежать возвращения большого набора данных в одном запросе.Enable server-driven paging, to avoid returning a large data set in one query. Дополнительные сведения см. в разделе подкачка, управляемая сервером.For more information, see Server-Driven Paging.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Требуется $filter и $orderby?Do you need $filter and $orderby? Некоторые приложения могут разрешать подкачку клиента с помощью $top и $skip, но отключать другие параметры запроса.Some applications might allow client paging, using $top and $skip, but disable the other query options.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Рекомендуется ограничивать $orderby свойствами в кластеризованном индексе.Consider restricting $orderby to properties in a clustered index. Сортировка больших данных без кластеризованного индекса выполняется слишком долго.Sorting large data without a clustered index is slow.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Максимальное число узлов: свойство макснодекаунт в [запрашиваемое] задает максимальное число узлов, допустимых в дереве синтаксиса $Filter.Maximum node count: The MaxNodeCount property on [Queryable] sets the maximum number nodes allowed in the $filter syntax tree. Значение по умолчанию — 100, но может потребоваться задать меньшее значение, так как большое количество узлов может замедляться для компиляции.The default value is 100, but you may want to set a lower value, because a large number of nodes can be slow to compile. Это особенно справедливо при использовании LINQ to Objects (т. е. запросов LINQ к коллекции в памяти без использования промежуточного поставщика LINQ).This is particularly true if you are using LINQ to Objects (i.e., LINQ queries on a collection in memory, without the use of an intermediate LINQ provider).

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

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Если какие-либо строковые свойства—содержат большие строки, например, описание продукта или запись—в блоге, попробуйте отключить строковые функции.If any string properties contain large strings—for example, a product description or a blog entry—consider disabling the string functions.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Рассмотрите возможность запрета фильтрации по свойствам навигации.Consider disallowing filtering on navigation properties. Фильтрация по свойствам навигации может привести к созданию объединения, которое может быть заработано в зависимости от схемы базы данных.Filtering on navigation properties can result in a join, which might be slow, depending on your database schema. В следующем коде показан проверяющий элемент управления, который предотвращает фильтрацию свойств навигации.The following code shows a query validator that prevents filtering on navigation properties. Дополнительные сведения о средствах проверки запросов см. в разделе Проверка запросов.For more information about query validators, see Query Validation.

    // 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 запросы, написав проверяющий элемент управления, настроенный для базы данных.Consider restricting $filter queries by writing a validator that is customized for your database. Например, рассмотрим следующие два запроса:For example, consider these two queries:

    • Все фильмы с субъектами, фамилии которых начинаются с "A".All movies with actors whose last name starts with ‘A'.

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

      Если фильмы не индексируются субъектами, для первого запроса может потребоваться, чтобы ядро СУБД проверяло весь список фильмов.Unless movies are indexed by actors, the first query might require the DB engine to scan the entire list of movies. В то время как второй запрос может быть приемлемым, при условии, что фильмы индексируются по году выпуска.Whereas the second query might be acceptable, assuming movies are indexed by release year.

      В следующем коде показан проверяющий элемент управления, позволяющий фильтровать свойства "Релеасэйеар" и "Title", но не другие свойства.The following code shows a validator that allows filtering on the "ReleaseYear" and "Title" properties but no other properties.

      // 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 вам нужны.In general, consider which $filter functions you need. Если клиентам не требуется полное выразительность $filter, можно ограничить разрешенные функции.If your clients do not need the full expressiveness of $filter, you can limit the allowed functions.