Abilitare le richieste tra le origini in API Web ASP.NET 2
Di Mike Wasson
Questo contenuto riguarda 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 le origini (CORS) in ASP.NET Core, vedere:
- Esercitazione: Creare un'API Web con ASP.NET Core
- Abilitare le richieste tra le origini (CORS) in ASP.NET Core
La sicurezza del browser impedisce a una pagina Web di creare richieste AJAX per un altro dominio. Questa restrizione viene chiamata criterio di stessa origine e impedisce a un sito dannoso di leggere i dati sensibili da un altro sito. Tuttavia, a volte potrebbe essere necessario consentire ad altri siti di chiamare l'API Web.
Condivisione di risorse tra le origini (CORS) è uno standard W3C che consente a un server di ridurre i criteri di stessa origine. Con CORS un server può consentire in modo esplicito alcune richieste multiorigine e rifiutarne altre. CORS è più sicuro e flessibile rispetto alle tecniche precedenti, ad esempio JSONP. Questa esercitazione illustra come abilitare CORS nell'applicazione API Web.
Software usato nell'esercitazione
- Visual Studio
- API Web 2.2
Introduzione
Questa esercitazione illustra il supporto 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 le origini.
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 diversohttp://example.com:9000/foo.html
- Porta diversahttps://example.com/foo.html
- Schema diversohttp://www.example.com/foo.html
- Sottodominio diverso
Nota
Internet Explorer non considera la porta durante il confronto delle origini.
Creare il progetto WebService
Nota
Questa sezione presuppone che si sappia già come creare progetti API Web. In caso contrario, vedere Introduzione alle API Web ASP.NET.
Avviare Visual Studio e creare un nuovo progetto applicazione Web (.NET Framework) ASP.NET.
Nella finestra di dialogo Nuova applicazione Web ASP.NET selezionare il modello di progetto Vuoto. In Aggiungi cartelle e riferimenti di base per selezionare la casella di controllo API Web.
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") }; } } }
È possibile eseguire l'applicazione in locale o distribuirla in Azure. Per gli screenshot di questa esercitazione, l'app viene distribuita in app Azure Servizio 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".
Creare il progetto WebClient
Creare un altro progetto applicazione Web (.NET Framework) ASP.NET e selezionare il modello di progetto MVC . Facoltativamente, selezionare Modifica autenticazione>senza autenticazione. Non è necessaria l'autenticazione per questa esercitazione.
In Esplora soluzioni aprire il file Views/Home/Index.cshtml. Sostituire il codice in questo file con il codice seguente:
<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.
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 le origini. Attualmente, l'app WebService non supporta CORS, quindi se si fa clic sul pulsante verrà visualizzato un errore.
Nota
Se si osserva 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 stessa origine non impediscono al browser di inviare la richiesta. Impedisce invece all'applicazione di visualizzare la risposta.
Abilitare CORS
A questo punto si abiliterà CORS nell'app WebService. Aggiungere prima di tutto il pacchetto NuGet CORS. In Visual Studio scegliere Gestione pacchetti NuGet dal menu Strumenti e quindi selezionare Gestione pacchetti Console. Nella finestra della 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 di base. Usare il -Version
flag per specificare come destinazione 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 origin usare l'URI in cui è stata distribuita l'applicazione WebClient. Ciò consente le richieste tra le origini da WebClient, pur non consentendo tutte le altre richieste tra domini. Più avanti, descriverò i parametri per [EnableCors] in modo più dettagliato.
Non includere una barra alla fine dell'URL di origine .
Ridistribuire l'applicazione WebService aggiornata. Non è necessario aggiornare WebClient. Ora la richiesta AJAX da WebClient dovrebbe avere esito positivo. I metodi GET, PUT e POST sono tutti consentiti.
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 da poter configurare correttamente l'attributo [EnableCors] e risolvere i problemi se gli elementi non funzionano come previsto.
La specifica CORS introduce diverse nuove intestazioni HTTP che abilitano le richieste tra le origini. Se un browser supporta CORS, imposta automaticamente queste intestazioni per le richieste tra le origini; non è necessario eseguire alcuna operazione speciale nel codice JavaScript.
Di seguito è riportato 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 oppure è il valore jolly "*", ovvero qualsiasi origine è consentita.
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 con esito positivo, il browser non rende disponibile la risposta all'applicazione client.
Richieste preliminari
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 sono soddisfatte le condizioni seguenti:
Il metodo di richiesta è 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 sull'oggetto XMLHttpRequest . La specifica CORS chiama queste "intestazioni di richiesta autore". La regola non si applica alle intestazioni che il browser può impostare, ad esempio User-Agent, Host o Content-Length.
Di seguito è riportato 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-anteprima usa il metodo HTTP OPTIONS. Include due intestazioni speciali:
- 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, non sono incluse intestazioni impostate dal browser.
Di seguito è riportato un esempio di risposta, 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 preliminari non inviano le intestazioni OPTIONS necessarie per impostazione predefinita. Verificare che le Access-Control-Request-Method
intestazioni e Access-Control-Request-Headers
vengano inviate con la richiesta e che le 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 web.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 consente alle richieste OPTIONS di ExtensionlessUrlHandler-Integrated-4.0
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] nel 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. L'esempio seguente abilita CORS per ogni metodo ad eccezione PutItem
di .
[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) { ... }
}
Globalmente
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 è:
- Azione
- Controller
- Generale
Impostare le origini consentite
Il parametro origin dell'attributo [EnableCors] specifica quali origini sono autorizzate ad 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 provenienti da qualsiasi origine.
Prendere in considerazione attentamente prima di consentire le 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 methods dell'attributo [EnableCors] specifica i metodi HTTP autorizzati ad accedere alla risorsa. Per consentire tutti i metodi, usare il valore jolly "*". L'esempio seguente consente solo le 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 ha 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 della richiesta di creazione"). Il parametro headers dell'attributo [EnableCors] specifica quali intestazioni di richiesta dell'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 "Accept", anche quando l'applicazione li imposta nello script.
Se si impostano intestazioni su qualsiasi elemento diverso da "*", è necessario includere almeno "accept", "content-type" e "origin", oltre a 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
- Scade il
- 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 esporrà 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 le origini
Le credenziali richiedono una gestione speciale in una richiesta CORS. Per impostazione predefinita, il browser non invia credenziali con una richiesta tra le origini. Le credenziali includono cookie e schemi di autenticazione HTTP. Per inviare le credenziali con una richiesta tra le 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 le 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 le origini.
Se il browser invia credenziali, ma la risposta non include un'intestazione Access-Control-Allow-Credentials valida, il browser non esporrà la risposta all'applicazione e la richiesta AJAX ha esito negativo.
Prestare attenzione all'impostazione di SupportsCredentials su true, perché significa che un sito Web in un altro dominio può inviare le credenziali di un utente connesso all'API Web per conto dell'utente, senza che l'utente sia a conoscenza. La specifica CORS indica inoltre che l'impostazione delle origini su "*" non è valida se SupportsCredentials è true.
Provider di criteri CORS personalizzati
L'attributo [EnableCors] implementa l'interfaccia ICorsPolicyProvider . È possibile fornire un'implementazione personalizzata creando una classe che deriva da Attribute 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 in cui inserire [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 dell'API Web è una tecnologia lato server. Anche il browser dell'utente deve supportare CORS. Fortunatamente, le versioni correnti di tutti i principali browser includono il supporto per CORS.