Bagikan melalui


Panduan Keamanan untuk ASP.NET Web API 2 OData

oleh Mike Wasson

Topik ini menjelaskan beberapa masalah keamanan yang harus Anda pertimbangkan saat mengekspos himpunan data melalui OData untuk ASP.NET Web API 2 pada ASP.NET 4.x.

Keamanan EDM

Semantik kueri didasarkan pada model data entitas (EDM), bukan jenis model yang mendasar. Anda dapat mengecualikan properti dari EDM dan properti tersebut tidak akan terlihat oleh kueri. Misalnya, model Anda menyertakan jenis Karyawan dengan properti Gaji. Anda mungkin ingin mengecualikan properti ini dari EDM untuk menyembunyikannya dari klien.

Ada dua cara untuk mengecualikan properti dari EDM. Anda dapat mengatur atribut [IgnoreDataMember] pada properti di kelas model:

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

Anda juga dapat menghapus properti dari EDM secara terprogram:

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

Keamanan Kueri

Klien jahat atau naif mungkin dapat membuat kueri yang membutuhkan waktu sangat lama untuk dijalankan. Dalam kasus terburuk ini dapat mengganggu akses ke layanan Anda.

Atribut [Queryable] adalah filter tindakan yang mengurai, memvalidasi, dan menerapkan kueri. Filter mengonversi opsi kueri menjadi ekspresi LINQ. Saat pengontrol OData mengembalikan jenis IQueryable , penyedia IQueryable LINQ mengonversi ekspresi LINQ menjadi kueri. Oleh karena itu, performa tergantung pada penyedia LINQ yang digunakan, dan juga pada karakteristik tertentu dari himpunan data atau skema database Anda.

Untuk informasi selengkapnya tentang menggunakan opsi kueri OData di ASP.NET Web API, lihat Mendukung Opsi Kueri OData.

Jika Anda tahu bahwa semua klien tepercaya (misalnya, di lingkungan perusahaan), atau jika himpunan data Anda kecil, performa kueri mungkin tidak menjadi masalah. Jika tidak, Anda harus mempertimbangkan rekomendasi berikut.

  • Uji layanan Anda dengan berbagai kueri dan profil DB.

  • Aktifkan halaman berbasis server, untuk menghindari pengembalian himpunan data besar dalam satu kueri. Untuk informasi selengkapnya, lihat Halaman Berbasis Server.

    // Enable server-driven paging.
    [Queryable(PageSize=10)]
    
  • Apakah Anda membutuhkan $filter dan $orderby? Beberapa aplikasi mungkin mengizinkan penomoran klien, menggunakan $top dan $skip, tetapi menonaktifkan opsi kueri lainnya.

    // Allow client paging but no other query options.
    [Queryable(AllowedQueryOptions=AllowedQueryOptions.Skip | 
                                   AllowedQueryOptions.Top)]
    
  • Pertimbangkan untuk membatasi $orderby ke properti dalam indeks berkluster. Mengurutkan data besar tanpa indeks berkluster lambat.

    // Set the allowed $orderby properties.
    [Queryable(AllowedOrderByProperties="Id,Name")] // Comma separated list
    
  • Jumlah simpul maksimum: Properti MaxNodeCount pada [Queryable] menetapkan jumlah maksimum simpul yang diizinkan di pohon sintaks $filter. Nilai defaultnya adalah 100, tetapi Anda mungkin ingin menetapkan nilai yang lebih rendah, karena sejumlah besar simpul bisa lambat untuk dikompilasi. Ini terutama berlaku jika Anda menggunakan LINQ ke Objek (yaitu, kueri LINQ pada koleksi dalam memori, tanpa menggunakan penyedia LINQ menengah).

    // Set the maximum node count.
    [Queryable(MaxNodeCount=20)]
    
  • Pertimbangkan untuk menonaktifkan fungsi any() dan all(), karena ini bisa lambat.

    // Disable any() and all() functions.
    [Queryable(AllowedFunctions= AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.All & ~AllowedFunctions.Any)]
    
  • Jika ada properti string yang berisi string besar—misalnya, deskripsi produk atau entri blog—pertimbangkan untuk menonaktifkan fungsi string.

    // Disable string functions.
    [Queryable(AllowedFunctions=AllowedFunctions.AllFunctions & 
        ~AllowedFunctions.AllStringFunctions)]
    
  • Pertimbangkan untuk melarang pemfilteran pada properti navigasi. Pemfilteran pada properti navigasi dapat mengakibatkan gabungan, yang mungkin lambat, tergantung pada skema database Anda. Kode berikut ini memperlihatkan validator kueri yang mencegah pemfilteran pada properti navigasi. Untuk informasi selengkapnya tentang validator kueri, lihat Validasi Kueri.

    // 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");
        }
    }
    
  • Pertimbangkan untuk membatasi kueri $filter dengan menulis validator yang dikustomisasi untuk database Anda. Misalnya, pertimbangkan dua kueri ini:

    • Semua film dengan aktor yang nama belakangnya dimulai dengan 'A'.

    • Semua film dirilis pada tahun 1994.

      Kecuali film diindeks oleh aktor, kueri pertama mungkin memerlukan mesin DB untuk memindai seluruh daftar film. Sedangkan kueri kedua mungkin dapat diterima, dengan asumsi film diindeks berdasarkan tahun rilis.

      Kode berikut menunjukkan validator yang memungkinkan pemfilteran pada properti "ReleaseYear" dan "Title" tetapi tidak ada properti lain.

      // 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);
          }
      }
      
  • Secara umum, pertimbangkan fungsi $filter mana yang Anda butuhkan. Jika klien Anda tidak memerlukan ekspresi penuh $filter, Anda dapat membatasi fungsi yang diizinkan.