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

作者:Mike Wasson

本主题介绍在 ASP.NET 4.x 上通过 OData 为 ASP.NET Web API 2 公开数据集时应考虑的一些安全问题。

EDM 安全性

查询语义基于 EDM) (实体数据模型,而不是基础模型类型。 可以从 EDM 中排除属性,该属性对查询不可见。 例如,假设模型包含具有 Salary 属性的 Employee 类型。 你可能希望从 EDM 中排除此属性,以将其从客户端中隐藏。

可通过两种方法从 EDM 中排除属性。 可以在模型类中的 属性上设置 [IgnoreDataMember] 属性:

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);

查询安全性

恶意或朴素的客户端可能能够构造需要很长时间才能执行的查询。 在最坏的情况下,这可能会中断对服务的访问。

[Queryable] 属性是一个操作筛选器,用于分析、验证和应用查询。 筛选器将查询选项转换为 LINQ 表达式。 当 OData 控制器返回 IQueryable 类型时, IQueryable LINQ 提供程序会将 LINQ 表达式转换为查询。 因此,性能取决于所使用的 LINQ 提供程序,还取决于数据集或数据库架构的特定特征。

有关在 ASP.NET Web API 中使用 OData 查询选项的详细信息,请参阅支持 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
    
  • 最大节点计数:[Queryable] 上的 MaxNodeCount 属性设置$filter语法树中允许的最大节点数。 默认值为 100,但你可能希望设置较低的值,因为大量节点的编译速度可能会很慢。 如果使用 LINQ to Objects (即对内存中的集合进行 LINQ 查询,而不使用中间 LINQ 提供程序) ,则尤其如此。

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • 请考虑禁用 any () 和所有 () 函数,因为这些函数可能很慢。

    // 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年上映的所有电影。

      除非影片由执行组件编制索引,否则第一个查询可能需要数据库引擎扫描整个电影列表。 假设电影按发行年份编制索引,则第二个查询可能是可以接受的。

      下面的代码显示了一个验证程序,它允许筛选“ReleaseYear”和“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的完整表达性,则可以限制允许的函数。