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的完整表达性,则可以限制允许的函数。