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

tarafından Mike Wasson

Bu konu başlığı altında, ASP.NET 4.x üzerinde ASP.NET Web API 2 için OData aracılığıyla bir veri kümesini kullanıma hazırlarken 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 sorgu tarafından görünmez. Örneğin, modelinizin Maaş özelliğine sahip bir Çalışan türü içerdiğini varsayalım. İstemcilerden gizlemek için bu özelliği EDM'nin dışında tutmak isteyebilirsiniz.

Bir özelliği EDM'den dışlamanı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ü niyetli veya saf bir istemci, yürütülmesi çok uzun sürebilecek bir sorgu oluşturabilir. 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 istemcilerin güvenilir olduğunu 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. Server-Driven Sayfalama.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • $filter ve $orderby gerekiyor mu? Bazı uygulamalar, $top ve $skip kullanarak istemci sayfalamasına 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'i kümelenmiş bir dizindeki özelliklerle sıralamayı 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 sayısını ayarlar. Varsayılan değer 100'dür, ancak daha düşük bir değer ayarlamak isteyebilirsiniz çünkü çok sayıda düğüm derleme işlemi yavaş olabilir. 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 (örneğin, ürün açıklaması veya blog girdisi) içeriyorsa 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 vermeyebilirsiniz. Gezinti özelliklerine göre filtreleme, veritabanı şemanıza bağlı olarak yavaş bir birleşime neden olabilir. Aşağıdaki kodda, gezinti özelliklerinde filtrelemeyi engelleyen bir sorgu doğrulayıcı gösterilmektedir. 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 göz atın:

    • Soyadı 'A' ile başlayan aktörlerin olduğu 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. İkinci sorgu kabul edilebilir olsa da filmlerin yayın yılına göre dizine alındığı varsayılır.

      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. Müşterilerinizin $filter'ün tüm ifade gücüne ihtiyacı yoksa, kullanılmasına izin verilen işlevleri sınırlayabilirsiniz.