ASP.NET Web API 2 OData için Güvenlik Kılavuzu

Tarafından Mike Wasson

Bu konuda, ASP.NET 4.x üzerinde ASP.NET Web API 2 için OData aracılığıyla bir veri kümesini kullanıma açarken dikkate almanız gereken bazı güvenlik sorunları açıklanmaktadır.

EDM Güvenliği

Sorgu semantiği, temel alınan model türlerini değil varlık veri modelini (EDM) temel alır. Bir özelliği EDM'nin dışında tutabilirsiniz ve bu özellik sorguya görünmez. Örneğin, modelinizde Maaş özelliğine sahip bir Çalışan türü olduğunu varsayalım. İstemcilerden gizlemek için bu özelliği EDM'nin dışında tutmak isteyebilirsiniz.

Bir özelliği EDM'nin dışında tutmanın iki yolu vardır. Model sınıfındaki özelliğinde [IgnoreDataMember] özniteliğini ayarlayabilirsiniz:

public class Employee
{
    public string Name { get; set; }
    public string Title { get; set; }
    [IgnoreDataMember]
    public decimal Salary { get; set; } // Not visible in the EDM
}

Ayrıca, özelliği program aracılığıyla EDM'den kaldırabilirsiniz:

var employees = modelBuilder.EntitySet<Employee>("Employees");
employees.EntityType.Ignore(emp => emp.Salary);

Sorgu Güvenliği

Kötü amaçlı veya saf bir istemci, yürütülmesi çok uzun süren bir sorgu oluşturabiliyor olabilir. En kötü durumda bu, hizmetinize erişimi kesintiye uğratabilir.

[Queryable] özniteliği sorguyu ayrıştıran, doğrulayan ve uygulayan bir eylem filtresidir. Filtre, sorgu seçeneklerini LINQ ifadesine dönüştürür. OData denetleyicisi bir IQueryable türü döndürdüğünde, IQueryable LINQ sağlayıcısı LINQ ifadesini sorguya dönüştürür. Bu nedenle performans, kullanılan LINQ sağlayıcısına ve ayrıca veri kümenizin veya veritabanı şemanızın belirli özelliklerine bağlıdır.

ASP.NET Web API'sinde OData sorgu seçeneklerini kullanma hakkında daha fazla bilgi için bkz . OData Sorgu Seçeneklerini Destekleme.

Tüm istemcilere güvenildiğini biliyorsanız (örneğin, kurumsal bir ortamda) veya veri kümeniz küçükse sorgu performansı sorun olmayabilir. Aksi takdirde, aşağıdaki önerileri dikkate almanız gerekir.

  • Hizmetinizi çeşitli sorgularla test edin ve veritabanı profilini oluşturun.

  • Tek bir sorguda büyük bir veri kümesini döndürmemek için sunucu temelli sayfalama özelliğini etkinleştirin. Daha fazla bilgi için bkz. Sunucu Temelli Sayfalama.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • $filter ve $orderby gerekiyor mu? Bazı uygulamalar, $top ve $skip kullanarak istemci disk belleğine izin verebilir, ancak diğer sorgu seçeneklerini devre dışı bırakabilir.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • $orderby kümelenmiş dizindeki özelliklerle kısıtlamayı göz önünde bulundurun. Kümelenmiş dizin olmadan büyük verileri sıralamak yavaştır.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • En fazla düğüm sayısı: [Queryable] üzerindeki MaxNodeCount özelliği, $filter söz dizimi ağacında izin verilen en fazla sayıda düğümü ayarlar. Varsayılan değer 100'dür, ancak daha düşük bir değer ayarlamak isteyebilirsiniz, çünkü çok sayıda düğüm yavaş derlenebilir. Bu durum özellikle LINQ to Objects kullanıyorsanız (örneğin, bir ara LINQ sağlayıcısı kullanmadan bellekteki bir koleksiyonda LINQ sorguları) geçerlidir.

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • Bunlar yavaş olabileceğinden any() ve all() işlevlerini devre dışı bırakmayı göz önünde bulundurun.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Herhangi bir dize özelliği büyük dizeler içeriyorsa (örneğin, ürün açıklaması veya blog girdisi), dize işlevlerini devre dışı bırakmayı göz önünde bulundurun.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Gezinti özelliklerinde filtrelemeye izin vermeyi göz önünde bulundurun. Gezinti özelliklerine göre filtreleme, veritabanı şemanıza bağlı olarak yavaş bir birleştirmeye neden olabilir. Aşağıdaki kod, gezinti özelliklerinde filtrelemeyi engelleyen bir sorgu doğrulayıcı gösterir. Sorgu doğrulayıcıları hakkında daha fazla bilgi için bkz. Sorgu Doğrulama.

    // 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");
        }
    }
    
  • Veritabanınız için özelleştirilmiş bir doğrulayıcı yazarak $filter sorgularını kısıtlamayı göz önünde bulundurun. Örneğin, şu iki sorguya dikkat edin:

    • Soyadı 'A' ile başlayan aktörlerin yer aldığı tüm filmler.

    • Tüm filmler 1994'te yayınlandı.

      Filmler aktörler tarafından dizine eklenmediği sürece ilk sorgu, db altyapısının film listesinin tamamını taramasını gerektirebilir. Filmlerin yayın yılına göre dizine alındığı varsayıldığında ikinci sorgu kabul edilebilir olabilir.

      Aşağıdaki kod, "ReleaseYear" ve "Title" özelliklerinde filtrelemeye izin veren ancak başka özellik içermeyen bir doğrulayıcı gösterir.

      // 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);
          }
      }
      
  • Genel olarak, hangi $filter işlevlerine ihtiyacınız olduğunu göz önünde bulundurun. İstemcilerinizin $filter tüm ifade özelliklerine ihtiyacı yoksa, izin verilen işlevleri sınırlayabilirsiniz.