安全指南为 ASP.NET Web API 2 ODataSecurity Guidance for ASP.NET Web API 2 OData

通过Mike Wassonby Mike Wasson

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

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. 例如,假设您的模型包含具有薪金属性的员工类型。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. 可以设置 [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.

[Queryable] 属性是操作筛选器,用于分析、 验证,并将查询应用。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. 有关详细信息,请参阅Server-Driven 分页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上的属性 [Queryable] 设置 $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.