Abilitare le richieste tra origini in API Web ASP.NET 2

Di Mike Wasson

Questo contenuto è per una versione precedente di .NET. Il nuovo sviluppo deve usare ASP.NET Core. Per altre informazioni sull'uso dell'API Web e delle richieste tra origini (CORS) in ASP.NET Core, vedere:

La sicurezza del browser impedisce a una pagina Web di creare richieste AJAX per un altro dominio. Questa restrizione viene chiamata criterio di origine stessa e impedisce a un sito dannoso di leggere i dati sensibili da un altro sito. Tuttavia, a volte si potrebbe voler consentire ad altri siti di chiamare l'API Web.

Cross Origin Resource Sharing (CORS) è uno standard W3C che consente a un server di rilassare i criteri di origine stessa. Con CORS un server può consentire in modo esplicito alcune richieste multiorigine e rifiutarne altre. CORS è più sicuro e più flessibile delle tecniche precedenti, ad esempio JSONP. Questa esercitazione illustra come abilitare CORS nell'applicazione API Web.

Software usato nell'esercitazione

Introduzione

Questa esercitazione illustra il supporto di CORS in API Web ASP.NET. Si inizierà creando due progetti ASP.NET, uno denominato "WebService", che ospita un controller API Web e l'altro denominato "WebClient", che chiama WebService. Poiché le due applicazioni sono ospitate in domini diversi, una richiesta AJAX da WebClient a WebService è una richiesta tra origini.

Mostra il servizio Web e il client Web

Che cos'è "stessa origine"?

Due URL hanno la stessa origine se hanno schemi, host e porte identici. (RFC 6454)

Questi due URL hanno la stessa origine:

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

Questi URL hanno origini diverse rispetto alle due precedenti:

  • http://example.net - Dominio diverso
  • http://example.com:9000/foo.html - Porta diversa
  • https://example.com/foo.html - Schema diverso
  • http://www.example.com/foo.html - Sottodominio diverso

Nota

Internet Explorer non considera la porta quando si confrontano le origini.

Creare il progetto WebService

Nota

Questa sezione presuppone che si sappia già come creare progetti API Web. In caso contrario, vedere Introduzione con API Web ASP.NET.

  1. Avviare Visual Studio e creare un nuovo progetto di applicazione Web (.NET Framework) di ASP.NET .

  2. Nella finestra di dialogo Nuova applicazione Web ASP.NET selezionare il modello di progetto Vuoto . In Aggiungi cartelle e riferimenti principali per selezionare la casella di controllo API Web .

    Finestra di dialogo Nuovo progetto ASP.NET in Visual Studio

  3. Aggiungere un controller API Web denominato TestController con il codice seguente:

    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. È possibile eseguire l'applicazione in locale o distribuire in Azure. Per gli screenshot in questa esercitazione, l'app viene distribuita in Servizio app di Azure App Web. Per verificare che l'API Web funzioni, passare a http://hostname/api/test/, dove hostname è il dominio in cui è stata distribuita l'applicazione. Verrà visualizzato il testo della risposta "GET: Test Message".

    Web browser che mostra il messaggio di test

Creare il progetto WebClient

  1. Creare un altro progetto ASP.NET applicazione Web (.NET Framework) e selezionare il modello di progetto MVC . Facoltativamente, selezionare Modifica autenticazionesenza autenticazione>. Non è necessaria l'autenticazione per questa esercitazione.

    Modello MVC nella finestra di dialogo Nuovo progetto ASP.NET in Visual Studio

  2. In Esplora soluzioni aprire il file Views/Home/Index.cshtml. Sostituire il codice in questo file con quanto segue:

    <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>
    }
    

    Per la variabile serviceUrl , usare l'URI dell'app WebService.

  3. Eseguire l'app WebClient in locale o pubblicarla in un altro sito Web.

Quando si fa clic sul pulsante "Prova" viene inviata una richiesta AJAX all'app WebService usando il metodo HTTP elencato nella casella a discesa (GET, POST o PUT). In questo modo è possibile esaminare diverse richieste tra origini. Attualmente, l'app WebService non supporta CORS, quindi se si fa clic sul pulsante verrà visualizzato un errore.

Errore 'Prova' nel browser

Nota

Se si watch il traffico HTTP in uno strumento come Fiddler, si noterà che il browser invia la richiesta GET e la richiesta ha esito positivo, ma la chiamata AJAX restituisce un errore. È importante comprendere che i criteri di origine stessi non impediscono al browser di inviare la richiesta. Impedisce invece all'applicazione di visualizzare la risposta.

Debugger Web fiddler che mostra le richieste Web

Abilitare CORS

A questo punto si abilita CORS nell'app WebService. Aggiungere prima di tutto il pacchetto NuGet CORS. In Visual Studio, dal menu Strumenti selezionare Gestione pacchetti NuGet, quindi selezionare Console di Gestione pacchetti. Nella finestra Console di Gestione pacchetti digitare il comando seguente:

Install-Package Microsoft.AspNet.WebApi.Cors

Questo comando installa il pacchetto più recente e aggiorna tutte le dipendenze, incluse le librerie api Web principali. Usare il -Version flag per eseguire la destinazione di una versione specifica. Il pacchetto CORS richiede l'API Web 2.0 o successiva.

Aprire il file App_Start/WebApiConfig.cs. Aggiungere il codice seguente al metodo 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 }
            );
        }
    }
}

Aggiungere quindi l'attributo [EnableCors] alla TestController classe:

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...
    }
}

Per il parametro origini , usare l'URI in cui è stata distribuita l'applicazione WebClient. Ciò consente richieste tra origini da WebClient, pur non consentendo comunque tutte le altre richieste tra domini. Più avanti, descriverò i parametri per [EnableCors] in modo più dettagliato.

Non includere una barra in avanti alla fine dell'URL di origine .

Ridistribuire l'applicazione WebService aggiornata. Non è necessario aggiornare WebClient. Ora la richiesta AJAX da WebClient deve avere esito positivo. I metodi GET, PUT e POST sono tutti consentiti.

Web browser che mostra un messaggio di test riuscito

Funzionamento di CORS

Questa sezione descrive cosa accade in una richiesta CORS, a livello di messaggi HTTP. È importante comprendere il funzionamento di CORS, in modo che sia possibile configurare correttamente l'attributo [EnableCors] e risolvere i problemi se le cose non funzionano come previsto.

La specifica CORS introduce diverse nuove intestazioni HTTP che consentono richieste tra origini. Se un browser supporta CORS, imposta queste intestazioni automaticamente per le richieste tra origini; non è necessario eseguire alcuna operazione speciale nel codice JavaScript.

Ecco un esempio di richiesta tra origini. L'intestazione "Origin" fornisce il dominio del sito che effettua la richiesta.

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

Se il server consente la richiesta, imposta l'intestazione Access-Control-Allow-Origin. Il valore di questa intestazione corrisponde all'intestazione Origin o è il valore jolly "*", ovvero è consentito qualsiasi origine.

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

Se la risposta non include l'intestazione Access-Control-Allow-Origin, la richiesta AJAX ha esito negativo. In particolare, il browser non consente la richiesta. Anche se il server restituisce una risposta riuscita, il browser non rende disponibile la risposta all'applicazione client.

Richieste di pre-verifica

Per alcune richieste CORS, il browser invia una richiesta aggiuntiva, denominata "richiesta preliminare", prima di inviare la richiesta effettiva per la risorsa.

Il browser può ignorare la richiesta preliminare se le condizioni seguenti sono vere:

  • Il metodo request è GET, HEAD o POST e

  • L'applicazione non imposta intestazioni di richiesta diverse da Accept, Accept-Language, Content-Language, Content-Type o Last-Event-ID e

  • L'intestazione Content-Type (se impostata) è una delle seguenti:

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

La regola sulle intestazioni di richiesta si applica alle intestazioni impostate dall'applicazione chiamando setRequestHeader nell'oggetto XMLHttpRequest . La specifica CORS chiama queste "intestazioni di richiesta di autore". La regola non si applica alle intestazioni che il browser può impostare, ad esempio User-Agent, Host o Content-Length.

Ecco un esempio di richiesta preliminare:

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

La richiesta di pre-volo usa il metodo HTTP OPTIONS. Include due intestazioni speciali:

  • Metodo Access-Control-Request-Method: metodo HTTP che verrà usato per la richiesta effettiva.
  • Access-Control-Request-Headers: elenco di intestazioni di richiesta impostate dall'applicazione nella richiesta effettiva. (Anche in questo caso, questa opzione non include intestazioni impostate dal browser).

Ecco una risposta di esempio, presupponendo che il server consenta la richiesta:

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

La risposta include un'intestazione Access-Control-Allow-Methods che elenca i metodi consentiti e facoltativamente un'intestazione Access-Control-Allow-Headers, che elenca le intestazioni consentite. Se la richiesta preliminare ha esito positivo, il browser invia la richiesta effettiva, come descritto in precedenza.

Gli strumenti comunemente usati per testare gli endpoint con richieste OPTIONS pre-volo (ad esempio , Fiddler e Postman) non inviano le intestazioni OPTIONS necessarie per impostazione predefinita. Verificare che le intestazioni e Access-Control-Request-Headers vengano inviate con la richiesta e che le Access-Control-Request-Method intestazioni OPTIONS raggiungano l'app tramite IIS.

Per configurare IIS per consentire a un'app ASP.NET di ricevere e gestire le richieste OPTION, aggiungere la configurazione seguente al file diweb.config dell'app nella <system.webServer><handlers> sezione:

<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>

La rimozione di impedisce a IIS di OPTIONSVerbHandler gestire le richieste OPTIONS. La sostituzione di ExtensionlessUrlHandler-Integrated-4.0 consente alle richieste OPTIONS di raggiungere l'app perché la registrazione predefinita del modulo consente solo le richieste GET, HEAD, POST e DEBUG con URL senza estensione.

Regole di ambito per [EnableCors]

È possibile abilitare CORS per azione, per controller o a livello globale per tutti i controller API Web nell'applicazione.

Per azione

Per abilitare CORS per una singola azione, impostare l'attributo [EnableCors] sul metodo di azione. L'esempio seguente abilita CORS solo per il GetItem metodo.

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 Controller

Se si imposta [EnableCors] nella classe controller, si applica a tutte le azioni nel controller. Per disabilitare CORS per un'azione, aggiungere l'attributo [DisableCors] all'azione. Nell'esempio seguente viene abilitato CORS per ogni metodo, ad eccezione PutItemdi .

[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) { ... }
}

A livello globale

Per abilitare CORS per tutti i controller API Web nell'applicazione, passare un'istanza enableCorsAttribute al metodo EnableCors :

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

Se si imposta l'attributo in più ambiti, l'ordine di precedenza è:

  1. Azione
  2. Controller
  3. Globale

Impostare le origini consentite

Il parametro di origine dell'attributo [EnableCors] specifica quali origini sono consentite per accedere alla risorsa. Il valore è un elenco delimitato da virgole delle origini consentite.

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

È anche possibile usare il valore jolly "*" per consentire le richieste da qualsiasi origine.

Prendere in considerazione attentamente prima di consentire richieste da qualsiasi origine. Significa che letteralmente qualsiasi sito Web può effettuare chiamate AJAX all'API Web.

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

Impostare i metodi HTTP consentiti

Il parametro dei metodi dell'attributo [EnableCors] specifica i metodi HTTP consentiti per accedere alla risorsa. Per consentire tutti i metodi, usare il valore jolly "*". L'esempio seguente consente solo richieste GET e POST.

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

Impostare le intestazioni di richiesta consentite

Questo articolo descritto in precedenza come una richiesta preliminare potrebbe includere un'intestazione Access-Control-Request-Headers, elencando le intestazioni HTTP impostate dall'applicazione (le cosiddette intestazioni di richiesta di creazione). Il parametro intestazioni dell'attributo [EnableCors] specifica quali intestazioni di richiesta di autore sono consentite. Per consentire le intestazioni, impostare le intestazioni su "*". Per consentire intestazioni specifiche, impostare le intestazioni su un elenco delimitato da virgole delle intestazioni consentite:

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

Tuttavia, i browser non sono completamente coerenti nel modo in cui impostano Access-Control-Request-Headers. Ad esempio, Chrome attualmente include "origin". FireFox non include intestazioni standard, ad esempio "Accetta", anche quando l'applicazione le imposta nello script.

Se si impostano intestazioni su qualsiasi elemento diverso da "*", è necessario includere almeno "accept", "content-type" e "origin", più eventuali intestazioni personalizzate che si desidera supportare.

Impostare le intestazioni di risposta consentite

Per impostazione predefinita, il browser non espone tutte le intestazioni di risposta all'applicazione. Le intestazioni di risposta disponibili per impostazione predefinita sono:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Scadenza
  • Last-Modified
  • Pragma

La specifica CORS chiama queste intestazioni di risposta semplici. Per rendere disponibili altre intestazioni all'applicazione, impostare il parametro exposedHeaders di [EnableCors].

Nell'esempio seguente il metodo del Get controller imposta un'intestazione personalizzata denominata 'X-Custom-Header'. Per impostazione predefinita, il browser non espone questa intestazione in una richiesta tra origini. Per rendere disponibile l'intestazione, includere 'X-Custom-Header' in 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;
    }
}

Passare le credenziali nelle richieste tra origini

Le credenziali richiedono una gestione speciale in una richiesta CORS. Per impostazione predefinita, il browser non invia credenziali con una richiesta di origine incrociata. Le credenziali includono cookie e schemi di autenticazione HTTP. Per inviare le credenziali con una richiesta tra origini, il client deve impostare XMLHttpRequest.withCredentials su true.

Uso diretto di XMLHttpRequest :

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

In jQuery:

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

Inoltre, il server deve consentire le credenziali. Per consentire le credenziali tra origini nell'API Web, impostare la proprietà SupportsCredentials su true nell'attributo [EnableCors] :

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

Se questa proprietà è true, la risposta HTTP includerà un'intestazione Access-Control-Allow-Credentials. Questa intestazione indica al browser che il server consente le credenziali per una richiesta tra origini.

Se il browser invia le credenziali, ma la risposta non include un'intestazione Access-Control-Allow-Credentials valida, il browser non espone la risposta all'applicazione e la richiesta AJAX ha esito negativo.

Prestare attenzione all'impostazione di SupportCredentials su true, perché significa che un sito Web in un altro dominio può inviare le credenziali dell'utente connesso all'API Web per conto dell'utente, senza che l'utente sia consapevole. La specifica CORS indica anche che l'impostazione delle origini su "*" non è valida se SupportCredentials è true.

Provider di criteri CORS personalizzati

L'attributo [EnableCors] implementa l'interfaccia ICorsPolicyProvider . È possibile fornire un'implementazione personalizzata creando una classe che deriva da Attributo e implementa 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);
    }
}

È ora possibile applicare l'attributo in qualsiasi posizione che verrà inserita [EnableCors].

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

Ad esempio, un provider di criteri CORS personalizzato potrebbe leggere le impostazioni da un file di configurazione.

In alternativa all'uso degli attributi, è possibile registrare un oggetto ICorsPolicyProviderFactory che crea oggetti ICorsPolicyProvider .

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

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

Per impostare ICorsPolicyProviderFactory, chiamare il metodo di estensione SetCorsPolicyProviderFactory all'avvio, come indicato di seguito:

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

        // ...
    }
}

Supporto browser

Il pacchetto CORS API Web è una tecnologia lato server. Il browser dell'utente deve anche supportare CORS. Fortunatamente, le versioni correnti di tutti i principali browser includono il supporto per CORS.