ASP.NET Web API 2 OData 的安全指南Security Guidance for ASP.NET Web API 2 OData

作者: Mike Wassonby Mike Wasson

本主题介绍在 ASP.NET 4.x 上通过 OData 公开 ASP.NET Web API 2 的数据集时应考虑的一些安全问题。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.

EDM 安全性EDM 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. 例如,假设您的模型包含一个具有 Salary 属性的雇员类型。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 类的属性中设置 [IgnoreDataMember] 属性: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.

有关在 ASP.NET Web API 中使用 OData 查询选项的详细信息,请参阅支持 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
    
  • 最大节点计数: [可查询] 上的MaxNodeCount属性设置 $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.

      下面的代码演示一个验证程序,该验证程序允许筛选 "ReleaseYear" 和 "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.