ASP.NET Web API 2 OData 安全指南
演講者:Mike Wasson
本主題介紹在 ASP.NET 4.x 上透過 OData for 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() 和 all() 函式,因為它們可能很慢。
// 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 的完整表達能力,您可以限制允許的功能。