共用方式為


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 的完整表達能力,您可以限制允許的功能。