Mengaktifkan permintaan lintas asal di ASP.NET Web API 2

Oleh Mike Wasson

Konten ini untuk versi .NET sebelumnya. Pengembangan baru harus menggunakan ASP.NET Core. Untuk informasi selengkapnya tentang menggunakan API Web dan Permintaan Lintas Asal (CORS) di ASP.NET Core, lihat:

Keamanan browser mencegah halaman web untuk membuat permintaan AJAX ke domain lain. Pembatasan ini disebut kebijakan asal yang sama, dan mencegah situs berbahaya membaca data sensitif dari situs lain. Namun, terkadang Anda mungkin ingin membiarkan situs lain memanggil API web Anda.

Cross Origin Resource Sharing (CORS) adalah standar W3C yang memungkinkan server untuk melonggarkan kebijakan asal yang sama. Dengan menggunakan CORS, server dapat secara eksplisit mengizinkan beberapa permintaan lintas-asal sekaligus menolak permintaan lainnya. CORS lebih aman dan lebih fleksibel daripada teknik sebelumnya seperti JSONP. Tutorial ini menunjukkan cara mengaktifkan CORS di aplikasi API Web Anda.

Perangkat lunak yang digunakan dalam tutorial

Pengantar

Tutorial ini menunjukkan dukungan CORS di ASP.NET Web API. Kita akan mulai dengan membuat dua proyek ASP.NET - yang disebut "WebService", yang menghosting pengontrol API Web, dan yang lainnya disebut "WebClient", yang memanggil WebService. Karena kedua aplikasi dihosting di domain yang berbeda, permintaan AJAX dari WebClient ke WebService adalah permintaan lintas asal.

Memperlihatkan layanan web dan klien web

Apa itu "asal yang sama"?

Dua URL memiliki asal yang sama jika memiliki skema, host, dan port yang identik. (RFC 6454)

Kedua URL ini memiliki asal yang sama:

  • http://example.com/foo.html
  • http://example.com/bar.html

URL ini memiliki asal yang berbeda dari dua sebelumnya:

  • http://example.net - Domain yang berbeda
  • http://example.com:9000/foo.html - Port yang berbeda
  • https://example.com/foo.html - Skema yang berbeda
  • http://www.example.com/foo.html - Subdomain yang berbeda

Catatan

Internet Explorer tidak mempertimbangkan port ketika membandingkan asal.

Membuat proyek WebService

Catatan

Bagian ini mengasumsikan Anda sudah tahu cara membuat proyek Web API. Jika tidak, lihat Memulai ASP.NET Web API.

  1. Mulai Visual Studio dan buat proyek ASP.NET Web Application (.NET Framework) baru.

  2. Dalam kotak dialog Aplikasi Web ASP.NET Baru , pilih templat Proyek kosong . Di bawah Tambahkan folder dan referensi inti untuk, pilih kotak centang API Web .

    Dialog proyek ASP.NET baru di Visual Studio

  3. Tambahkan pengontrol API Web bernama TestController dengan kode berikut:

    using System.Net.Http;
    using System.Web.Http;
    
    namespace WebService.Controllers
    {
        public class TestController : ApiController
        {
            public HttpResponseMessage Get()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("GET: Test message")
                };
            }
    
            public HttpResponseMessage Post()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("POST: Test message")
                };
            }
    
            public HttpResponseMessage Put()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("PUT: Test message")
                };
            }
        }
    }
    
  4. Anda dapat menjalankan aplikasi secara lokal atau menyebarkan ke Azure. (Untuk cuplikan layar dalam tutorial ini, aplikasi disebarkan ke Azure App Service Web Apps.) Untuk memverifikasi bahwa API web berfungsi, navigasikan ke http://hostname/api/test/, di mana nama host adalah domain tempat Anda menyebarkan aplikasi. Anda akan melihat teks respons, "GET: Test Message".

    Browser web memperlihatkan pesan pengujian

Membuat proyek WebClient

  1. Buat proyek ASP.NET Web Application (.NET Framework) lain dan pilih templat proyek MVC. Secara opsional, pilih Ubah Autentikasi>Tanpa Autentikasi. Anda tidak memerlukan autentikasi untuk tutorial ini.

    Templat MVC dalam dialog Proyek ASP.NET Baru di Visual Studio

  2. Di Penjelajah Solusi, buka file Views/Home/Index.cshtml. Ganti kode dalam file ini dengan yang berikut ini:

    <div>
        <select id="method">
            <option value="get">GET</option>
            <option value="post">POST</option>
            <option value="put">PUT</option>
        </select>
        <input type="button" value="Try it" onclick="sendRequest()" />
        <span id='value1'>(Result)</span>
    </div>
    
    @section scripts {
    <script>
        // TODO: Replace with the URL of your WebService app
        var serviceUrl = 'http://mywebservice/api/test'; 
    
        function sendRequest() {
            var method = $('#method').val();
    
            $.ajax({
                type: method,
                url: serviceUrl
            }).done(function (data) {
                $('#value1').text(data);
            }).fail(function (jqXHR, textStatus, errorThrown) {
                $('#value1').text(jqXHR.responseText || textStatus);
            });
        }
    </script>
    }
    

    Untuk variabel serviceUrl , gunakan URI aplikasi WebService.

  3. Jalankan aplikasi WebClient secara lokal atau terbitkan ke situs web lain.

Saat Anda mengklik tombol "Coba", permintaan AJAX dikirimkan ke aplikasi WebService menggunakan metode HTTP yang tercantum dalam kotak dropdown (GET, POST, atau PUT). Ini memungkinkan Anda memeriksa permintaan lintas asal yang berbeda. Saat ini, aplikasi WebService tidak mendukung CORS, jadi jika Anda mengklik tombol, Anda akan mendapatkan kesalahan.

Kesalahan 'Coba' di browser

Catatan

Jika Anda watch lalu lintas HTTP di alat seperti Fiddler, Anda akan melihat bahwa browser mengirim permintaan GET, dan permintaan berhasil, tetapi panggilan AJAX mengembalikan kesalahan. Penting untuk dipahami bahwa kebijakan asal yang sama tidak mencegah browser mengirim permintaan. Sebaliknya, ini mencegah aplikasi melihat respons.

Debugger web Fiddler memperlihatkan permintaan web

Mengaktifkan CORS

Sekarang mari kita aktifkan CORS di aplikasi WebService. Pertama, tambahkan paket CORS NuGet. Di Visual Studio, dari menu Alat , pilih Manajer Paket NuGet, lalu pilih Konsol Manajer Paket. Di jendela Konsol Manajer Paket, ketik perintah berikut:

Install-Package Microsoft.AspNet.WebApi.Cors

Perintah ini menginstal paket terbaru dan memperbarui semua dependensi, termasuk pustaka API Web inti. -Version Gunakan bendera untuk menargetkan versi tertentu. Paket CORS memerlukan Web API 2.0 atau yang lebih baru.

Buka file App_Start/WebApiConfig.cs. Tambahkan kode berikut ke metode WebApiConfig.Register :

using System.Web.Http;
namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // New code
            config.EnableCors();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

Selanjutnya, tambahkan atribut [EnableCors] ke TestController kelas :

using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
    public class TestController : ApiController
    {
        // Controller methods not shown...
    }
}

Untuk parameter asal , gunakan URI tempat Anda menyebarkan aplikasi WebClient. Ini memungkinkan permintaan lintas asal dari WebClient, sambil tetap melarang semua permintaan lintas domain lainnya. Nantinya, saya akan menjelaskan parameter untuk [EnableCors] secara lebih rinci.

Jangan sertakan garis miring di akhir URL asal .

Sebarkan ulang aplikasi WebService yang diperbarui. Anda tidak perlu memperbarui WebClient. Sekarang permintaan AJAX dari WebClient harus berhasil. Metode GET, PUT, dan POST semuanya diizinkan.

Browser web memperlihatkan pesan pengujian yang berhasil

Cara Kerja CORS

Bagian ini menjelaskan apa yang terjadi dalam permintaan CORS, pada tingkat pesan HTTP. Penting untuk memahami cara kerja CORS, sehingga Anda dapat mengonfigurasi atribut [EnableCors] dengan benar dan memecahkan masalah jika semuanya tidak berfungsi seperti yang Anda harapkan.

Spesifikasi CORS memperkenalkan beberapa header HTTP baru yang memungkinkan permintaan lintas asal. Jika browser mendukung CORS, ia mengatur header ini secara otomatis untuk permintaan lintas asal; Anda tidak perlu melakukan sesuatu yang istimewa dalam kode JavaScript Anda.

Berikut adalah contoh permintaan lintas asal. Header "Asal" memberi domain situs yang membuat permintaan.

GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net

Jika server mengizinkan permintaan, server akan mengatur header Access-Control-Allow-Origin. Nilai header ini cocok dengan header Asal, atau merupakan nilai wildcard "*", yang berarti bahwa asal apa pun diizinkan.

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17

GET: Test message

Jika respons tidak menyertakan header Access-Control-Allow-Origin, permintaan AJAX gagal. Secara khusus, browser melarang permintaan. Bahkan jika server mengembalikan respons yang berhasil, browser tidak membuat respons tersedia untuk aplikasi klien.

Permintaan Preflight

Untuk beberapa permintaan CORS, browser mengirimkan permintaan tambahan, yang disebut "permintaan preflight", sebelum mengirim permintaan aktual untuk sumber daya.

Browser dapat melewati permintaan preflight jika kondisi berikut ini benar:

  • Metode permintaan adalah GET, HEAD, atau POST, dan

  • Aplikasi tidak mengatur header permintaan apa pun selain Terima, Terima-Bahasa, Bahasa Konten, Jenis Konten, atau ID-Peristiwa-Terakhir, dan

  • Header Content-Type (jika diatur) adalah salah satu dari berikut ini:

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Aturan tentang header permintaan berlaku untuk header yang ditetapkan aplikasi dengan memanggil setRequestHeader pada objek XMLHttpRequest . (Spesifikasi CORS memanggil "header permintaan pembuat" ini.) Aturan tidak berlaku untuk header yang dapat diatur browser , seperti User-Agent, Host, atau Content-Length.

Berikut adalah contoh permintaan preflight:

OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0

Permintaan pra-penerbangan menggunakan metode HTTP OPTIONS. Ini termasuk dua header khusus:

  • Access-Control-Request-Method: Metode HTTP yang akan digunakan untuk permintaan aktual.
  • Access-Control-Request-Headers: Daftar header permintaan yang ditetapkan aplikasi pada permintaan aktual. (Sekali lagi, ini tidak termasuk header yang ditetapkan browser.)

Berikut adalah contoh respons, dengan asumsi bahwa server mengizinkan permintaan:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 05 Jun 2013 06:33:22 GMT

Respons mencakup header Access-Control-Allow-Methods yang mencantumkan metode yang diizinkan, dan secara opsional header Access-Control-Allow-Headers, yang mencantumkan header yang diizinkan. Jika permintaan preflight berhasil, browser mengirimkan permintaan aktual, seperti yang dijelaskan sebelumnya.

Alat yang umum digunakan untuk menguji titik akhir dengan permintaan OPTIONS preflight (misalnya, Fiddler dan Postman) tidak mengirim header OPTIONS yang diperlukan secara default. Konfirmasikan bahwa Access-Control-Request-Method header dan Access-Control-Request-Headers dikirim dengan permintaan dan header OPTIONS mencapai aplikasi melalui IIS.

Untuk mengonfigurasi IIS agar aplikasi ASP.NET dapat menerima dan menangani permintaan OPTION, tambahkan konfigurasi berikut ke file web.config aplikasi di bagian <system.webServer><handlers> :

<system.webServer>
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="OPTIONSVerbHandler" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

OPTIONSVerbHandler Penghapusan mencegah IIS menangani permintaan OPTIONS. Penggantian ExtensionlessUrlHandler-Integrated-4.0 memungkinkan permintaan OPTIONS untuk menjangkau aplikasi karena pendaftaran modul default hanya memungkinkan permintaan GET, HEAD, POST, dan DEBUG dengan URL tanpa ekstensi.

Aturan Cakupan untuk [EnableCors]

Anda dapat mengaktifkan CORS per tindakan, per pengontrol, atau secara global untuk semua pengontrol API Web di aplikasi Anda.

Per Tindakan

Untuk mengaktifkan CORS untuk satu tindakan, atur atribut [EnableCors] pada metode tindakan. Contoh berikut mengaktifkan CORS hanya untuk metode .GetItem

public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public HttpResponseMessage GetItem(int id) { ... }

    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage PutItem(int id) { ... }
}

Per Pengontrol

Jika Anda mengatur [EnableCors] pada kelas pengontrol, itu berlaku untuk semua tindakan pada pengontrol. Untuk menonaktifkan CORS untuk tindakan, tambahkan atribut [DisableCors] ke tindakan. Contoh berikut memungkinkan CORS untuk setiap metode kecuali PutItem.

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }

    [DisableCors]
    public HttpResponseMessage PutItem(int id) { ... }
}

Secara global

Untuk mengaktifkan CORS untuk semua pengontrol API Web di aplikasi Anda, teruskan instans EnableCorsAttribute ke metode EnableCors :

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("www.example.com", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

Jika Anda mengatur atribut pada lebih dari satu cakupan, urutan prioritasnya adalah:

  1. Tindakan
  2. Pengontrol
  3. Global

Mengatur asal yang diizinkan

Parameter asal atribut [EnableCors] menentukan asal mana yang diizinkan untuk mengakses sumber daya. Nilainya adalah daftar asal yang diizinkan yang dipisahkan koma.

[EnableCors(origins: "http://www.contoso.com,http://www.example.com", 
    headers: "*", methods: "*")]

Anda juga dapat menggunakan nilai kartubebas "*" untuk mengizinkan permintaan dari asal apa pun.

Pertimbangkan dengan cermat sebelum mengizinkan permintaan dari asal apa pun. Ini berarti bahwa secara harfiah situs web apa pun dapat melakukan panggilan AJAX ke API web Anda.

// Allow CORS for all origins. (Caution!)
[EnableCors(origins: "*", headers: "*", methods: "*")]

Mengatur metode HTTP yang diizinkan

Parameter metode dari atribut [EnableCors] menentukan metode HTTP mana yang diizinkan untuk mengakses sumber daya. Untuk mengizinkan semua metode, gunakan nilai kartubebas "*". Contoh berikut hanya mengizinkan permintaan GET dan POST.

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
    public HttpResponseMessage Get() { ... }
    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage Put() { ... }    
}

Mengatur header permintaan yang diizinkan

Artikel ini menjelaskan sebelumnya bagaimana permintaan preflight mungkin menyertakan header Access-Control-Request-Headers, mencantumkan header HTTP yang ditetapkan oleh aplikasi (yang disebut "header permintaan pembuat"). Parameter header dari atribut [EnableCors] menentukan header permintaan pembuat mana yang diizinkan. Untuk mengizinkan header apa pun, atur header ke "*". Untuk memperbolehkan header tertentu, atur header ke daftar header yang dipisahkan koma:

[EnableCors(origins: "http://example.com", 
    headers: "accept,content-type,origin,x-my-header", methods: "*")]

Namun, browser tidak sepenuhnya konsisten dalam cara mereka mengatur Access-Control-Request-Headers. Misalnya, Chrome saat ini menyertakan "origin". FireFox tidak menyertakan header standar seperti "Terima", bahkan ketika aplikasi mengaturnya dalam skrip.

Jika Anda mengatur header ke apa pun selain "*", Anda harus menyertakan setidaknya "terima", "jenis konten", dan "asal", ditambah header kustom apa pun yang ingin Anda dukung.

Mengatur header respons yang diizinkan

Secara default, browser tidak mengekspos semua header respons ke aplikasi. Header respons yang tersedia secara default adalah:

  • Cache-Kontrol
  • Bahasa-Konten
  • Jenis-Konten
  • Kedaluwarsa
  • Terakhir Diubah
  • Pragma

Spesifikasi CORS memanggil header respons sederhana ini. Untuk membuat header lain tersedia untuk aplikasi, atur parameter exposedHeaders dari [EnableCors].

Dalam contoh berikut, metode pengontrol Get mengatur header kustom bernama 'X-Custom-Header'. Secara default, browser tidak akan mengekspos header ini dalam permintaan lintas asal. Untuk membuat header tersedia, sertakan 'X-Custom-Header' di exposedHeaders.

[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var resp = new HttpResponseMessage()
        {
            Content = new StringContent("GET: Test message")
        };
        resp.Headers.Add("X-Custom-Header", "hello");
        return resp;
    }
}

Meneruskan kredensial dalam permintaan lintas asal

Kredensial memerlukan penanganan khusus dalam permintaan CORS. Secara default, browser tidak mengirim kredensial apa pun dengan permintaan lintas asal. Kredensial mencakup cookie serta skema autentikasi HTTP. Untuk mengirim kredensial dengan permintaan lintas asal, klien harus mengatur XMLHttpRequest.withCredentials ke true.

Menggunakan XMLHttpRequest secara langsung:

var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;

Di jQuery:

$.ajax({
    type: 'get',
    url: 'http://www.example.com/api/test',
    xhrFields: {
        withCredentials: true
    }

Selain itu, server harus mengizinkan kredensial. Untuk mengizinkan kredensial lintas asal di API Web, atur properti SupportsCredentials ke true pada atribut [EnableCors ]:

[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*", 
    methods: "*", SupportsCredentials = true)]

Jika properti ini benar, respons HTTP akan menyertakan header Access-Control-Allow-Credentials. Header ini memberi tahu browser bahwa server mengizinkan kredensial untuk permintaan lintas asal.

Jika browser mengirim kredensial, tetapi respons tidak menyertakan header Access-Control-Allow-Credentials yang valid, browser tidak akan mengekspos respons terhadap aplikasi, dan permintaan AJAX gagal.

Berhati-hatilah dalam mengatur SupportsCredentials ke true, karena itu berarti situs web di domain lain dapat mengirim kredensial pengguna yang masuk ke API Web Anda atas nama pengguna, tanpa diketahui pengguna. Spesifikasi CORS juga menyatakan bahwa pengaturan asal ke "*" tidak valid jika SupportsCredentials benar.

Penyedia kebijakan CORS kustom

Atribut [EnableCors] mengimplementasikan antarmuka ICorsPolicyProvider . Anda dapat menyediakan implementasi Anda sendiri dengan membuat kelas yang berasal dari Atribut dan mengimplementasikan ICorsPolicyProvider.

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider 
{
    private CorsPolicy _policy;

    public MyCorsPolicyAttribute()
    {
        // Create a CORS policy.
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // Add allowed origins.
        _policy.Origins.Add("http://myclient.azurewebsites.net");
        _policy.Origins.Add("http://www.contoso.com");
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

Sekarang Anda dapat menerapkan atribut di mana saja yang akan Anda letakkan [EnableCors].

[MyCorsPolicy]
public class TestController : ApiController
{
    .. //

Misalnya, penyedia kebijakan CORS kustom dapat membaca pengaturan dari file konfigurasi.

Sebagai alternatif untuk menggunakan atribut, Anda dapat mendaftarkan objek ICorsPolicyProviderFactory yang membuat objek ICorsPolicyProvider .

public class CorsPolicyFactory : ICorsPolicyProviderFactory
{
    ICorsPolicyProvider _provider = new MyCorsPolicyProvider();

    public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
    {
        return _provider;
    }
}

Untuk mengatur ICorsPolicyProviderFactory, panggil metode ekstensi SetCorsPolicyProviderFactory saat startup, sebagai berikut:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
        config.EnableCors();

        // ...
    }
}

Dukungan browser

Paket CORS WEB API adalah teknologi sisi server. Browser pengguna juga perlu mendukung CORS. Untungnya, versi saat ini dari semua browser utama menyertakan dukungan untuk CORS.