Partager via


API Web ASP.NET

Prise en charge de CORS dans l'API Web ASP.NET 2

Brock Allen

Le partage de ressources d'origines croisées (CORS, Cross Origin Resource Sharing) est une spécification W3C (World Wide Web Consortium), souvent considérée comme faisant partie de HTML5, qui laisse JavaScript déroger à la restriction de sécurité de stratégie de même origine imposée par les navigateurs. La stratégie de même origine signifie que votre JavaScript peut uniquement effectuer des rappels AJAX vers la même origine que celle de la page Web le contenant (dans laquelle l'« origine » est définie comme étant la combinaison du nom d'hôte, du protocole et du numéro de port). Par exemple, JavaScript sur une page Web http://foo.com ne peut pas effectuer d'appels AJAX vers http://bar.com (pas plus que vers http://www.foo.com, https://foo.com ou http://foo.com:999).

CORS allège cette restriction en permettant aux serveurs d'indiquer les origines autorisées à les appeler. CORS est appliquée par les navigateurs mais doit être implémentée sur le serveur. La toute dernière version de l'API Web ASP.NET 2 intègre une prise en charge complète de CORS. Avec l'API Web 2, vous pouvez configurer la stratégie pour permettre aux clients JavaScript d'une autre origine d'accéder à vos API.

Principes fondamentaux de CORS

Pour utiliser les nouvelles fonctionnalités de CORS dans l'API Web, il est utile de comprendre les détails de CORS elle-même, car l'implémentation de l'API Web respecte la spécification. Ces détails vous sembleront peut-être exagérés pour le moment, mais ils seront précieux par la suite pour comprendre les paramètres disponibles dans l'API Web. En outre, lors du débogage de CORS, ils vous aideront à résoudre les problèmes plus rapidement.

La mécanique générale de CORS est telle que, lorsque JavaScript essaie d'effectuer un appel AJAX d'origine croisée, le navigateur « demande » au serveur si cette action est autorisée en envoyant des en-têtes dans la requête HTTP (par exemple, Origin). Le serveur indique ce qui est autorisé en retournant les en-têtes HTPP dans la réponse (par exemple, Access-Control-Allow-Origin). La vérification des autorisations est effectuée pour chaque URL distincte invoquée par le client, ce qui signifie que différentes URL peuvent avoir différentes autorisations.

Outre l'origine, CORS permet à un serveur d'indiquer les méthodes HTTP autorisées, les en-têtes de requête HTTP qu'un client peut envoyer, les en-têtes de réponse HTTP qu'un client peut lire, et si le navigateur est autorisé à envoyer ou à recevoir des informations d'identification (cookies ou en-têtes d'autorisation). Les en-têtes de requête et de réponse supplémentaires indiquent les fonctionnalités qui sont autorisées. Ces en-têtes sont présentés dans la figure 1 (remarquez que certaines des fonctionnalités n'ont aucun en-tête dans la requête ; elles en comportent seulement dans la réponse).

Figure 1 En-têtes HTTP CORS

Autorisation/Fonctionnalité En-tête de requête En-tête de réponse
Origine Origin Access-Control-Allow-Origin
Méthode HTTP Access-Control-Request-Method Access-Control-Allow-Method
En-têtes de requête Access-Control-Request-Headers Access-Control-Allow-Headers
En-têtes de réponse   Access-Control-Expose-Headers
Informations d'identification   Access-Control-Allow-Credentials
Réponse préliminaire du cache   Access-Control-Max-Age

Les navigateurs peuvent demander au serveur ces autorisations de deux façons différentes : requêtes CORS simples et requêtes CORS préliminaires.

Requêtes CORS simples Voici un exemple de requête CORS simple :

POST http://localhost/WebApiCorsServer/Resources/ HTTP/1.1
Host: localhost
Accept: */*
Origin: http://localhost:55912
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
value1=foo&value2=5

Et la réponse :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912
Content-Length: 27
{"Value1":"foo","Value2":5}

La requête est une requête d'origine croisée de http://localhost:55912 à http://localhost, et le navigateur ajoute un en-tête HTTP Origin dans la requête pour indiquer l'origine appelante au serveur. Le serveur répond avec un en-tête de réponse Access-Control-Allow-Origin qui indique que cette origine est autorisée. Le navigateur applique la stratégie de serveur et le JavaScript recevra son rappel normal de réussite.

Le serveur peut répondre avec la valeur exacte d'origine de la requête ou la valeur « * » qui indique que toute origine est autorisée. Si le serveur n'avait pas autorisé l'origine appelante, l'en-tête Access-Control-Allow-Origin aurait simplement été absent et le rappel d'erreur du JavaScript appelant aurait été invoqué.

Notez qu'avec une requête CORS simple, l'appel sur le serveur est toujours invoqué. Cela peut être surprenant si vous ne connaissez pas encore bien CORS, mais ce comportement n'est pas différent d'un scénario dans lequel le navigateur aurait construit un élément <form> et effectué une requête POST normale. CORS n'empêche pas l'appel d'être invoqué sur le serveur, elle empêche plutôt le JavaScript appelant de recevoir les résultats. Si vous voulez empêcher l'appelant d'invoquer le serveur, vous devrez ensuite implémenter un type d'autorisation dans votre code serveur (éventuellement avec l'attribut du filtre d'autorisation [Authorize]).

L'exemple précédent est connu sous le nom de requête CORS simple, car le type d'appel AJAX depuis le client était un GET ou un POST ; le Content-Type était application/x-www-form-urlencoded, multipart/form-data ou text/plain ; et aucun en-tête de requête supplémentaire n'a été envoyé. Si l'appel AJAX était une autre méthode HTTP, si le Content-Type était une autre valeur ou si le client voulait envoyer des en-têtes de requête supplémentaires, la requête serait alors considérée comme une requête préliminaire. La mécanique des requêtes préliminaires est légèrement différente.

Requêtes CORS préliminaires Si un appel AJAX n'est pas une requête simple, il requiert alors une requête CORS préliminaire, qui est simplement une requête HTTP supplémentaire au serveur pour obtenir une autorisation. Cette requête préliminaire est effectuée automatiquement par le navigateur et utilise la méthode HTTP OPTIONS. Si le serveur répond avec succès à la requête préliminaire et accorde une autorisation, le navigateur se charge ensuite de l'appel AJAX lui-même que le JavaScript essaie d'effectuer.

Si les performances jouent un rôle important (et quand n'est-ce pas le cas ?), le résultat de cette requête préliminaire peut être mis en cache par le navigateur en intégrant l'en-tête Access-Control-Max-Age dans la réponse préliminaire. La valeur contient le nombre de secondes pendant lesquelles les autorisations peuvent être mises en cache.

Voici un exemple de requête CORS préliminaire :

OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Access-Control-Request-Method: PUT
Origin: http://localhost:55912
Access-Control-Request-Headers: content-type
Accept: */*

Et la réponse préliminaire :

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: content-typeAccess-Control-Max-Age: 600

Voici la requête AJAX elle-même :

PUT http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Content-Length: 27
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:55912
Content-Type: application/json
{"value1":"foo","value2":5}

Et la réponse AJAX :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912
Content-Length: 27
{"Value1":"foo","Value2":5}

Notez que, dans cette exemple, une requête CORS préliminaire est déclenchée, car la méthode HTTP est PUT et le client doit envoyer l'en-tête Content-Type pour indiquer que la requête contient application/json. Dans la requête préliminaire (en plus d'Origin), les en-têtes de requête Access-Control-Request-Method et Access-Control-Request-Headers sont utilisées pour demander l'autorisation sur le type de méthode HTTP et l'en-tête supplémentaire que le client souhaite envoyer.

Le serveur a accordé l'autorisation (et définit une durée de cache préliminaire), puis le navigateur a autorisé le véritable appel AJAX. Si le serveur n'avait pas accordé l'autorisation aux fonctionnalités requises, l'en-tête de réponse correspondante n'aurait pas été absente, l'appel AJAX n'aurait pas été effectué et le rappel d'erreur JavaScript n'aurait pas été invoqué à la place.

Les requêtes et réponses HTTP précédentes ont été faites à l'aide de Firefox. Si vous deviez utiliser Internet Explorer, vous remarqueriez ensuite un en-tête Accept supplémentaire requis. Si vous deviez utiliser Chrome, vous verriez ensuite Accept et Origin requis en plus. Il est intéressant de noter que vous ne verrez pas Accept ni Origin dans les Access-Control-Allow-Headers, comme la spécification indique qu'ils sont implicites et peuvent être omis (ce que fait l'API Web). Il s'agit d'un point de débat si Origin et Accept doivent en fait être requis, mais, compte tenu de la façon dont ces navigateurs fonctionnent aujourd'hui, votre stratégie CORS d'API Web devra probablement les inclure. Malheureusement, les fournisseurs de navigateurs ne semblent pas être cohérents dans leur lecture de la spécification.

En-têtes de réponse Il est facile d'accorder au client l'autorisation d'accéder aux en-têtes de réponse à l'aide de l'en-tête de réponse Access-Control-Expose-Headers. Voici un exemple de réponse HTTP qui autorise le JavaScript appelant à accéder à la « barre » d'en-têtes de réponse personnalisée :

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912Access-Control-Expose-Headers: bar
bar: a bar value
Content-Length: 27
{"Value1":"foo","Value2":5}

Le client JavaScript peut simplement utiliser la fonction XMLHttpRequest getResponseHeader pour lire la valeur. Voici un exemple d'utilisation de jQuery :

$.ajax({
  url: "http://localhost/WebApiCorsServer/Resources/1",
  // other settings omitted
}).done(function (data, status, xhr) {
  var bar = xhr.getResponseHeader("bar");
  alert(bar);
});

Informations d'identification et authentification Probablement l'aspect le plus complexe de CORS concerne les informations d'identification et l'authentification. De manière générale, l'authentification avec les API Web peut être effectuée avec un cookie ou un en-tête Authorization (il existe deux façons, mais celles-ci sont les plus courantes). Dans une activité de navigation normale, si l'une d'entre elles a été précédemment établie, le navigateur passera ensuite implicitement ces valeurs au serveur sur les requêtes suivantes. Avec AJAX d'origine croisée, cependant, ce passage implicite des valeurs doit être explicitement requis dans JavaScript (via l'indicateur withCredentials sur XMLHttpRequest) et doit être explicitement autorisé dans la stratégie CORS du serveur (via l'en-tête de réponse Access-Control-Allow-Credentials).

Voici l'exemple d'un client JavaScript qui définit l'indicateur withCredentials avec jQuery :

$.ajax({
  url: "http://localhost/WebApiCorsServer/Resources/1",
  xhrFields: {
    withCredentials: true
  }
  // Other settings omitted
});

L'indicateur withCredentials effectue deux actions : Si le serveur émet un cookie, le navigateur peut l'accepter ; si le navigateur possède un cookie, il peut l'envoyer au serveur.

Voici un exemple de réponse HTTP qui autorise les informations d'identification :

HTTP/1.1 200 OK
Set-Cookie: foo=1379020091825
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Allow-Credentials: true

L'en-tête de réponse Access-Control-Allow-Credentials effectue deux actions : Si la réponse possède un cookie, le navigateur peut l'accepter. Si le navigateur a envoyé un cookie sur la requête, le client JavaScript peut recevoir les résultats de l'appel. En d'autres termes, si le client définit withCredentials, le client voit uniquement un rappel de réussite dans le JavaScript si le serveur (dans la réponse) autorise les informations d'identification. Si withCredentials a été défini et que le serveur n'autorise pas les informations d'identification, le client n'obtient pas l'accès aux résultats et le rappel d'erreur du client est invoqué.

Le même ensemble de règles et de comportements s'applique si l'en-tête Authorization est utilisé au lieu de cookies (par exemple, lors de l'utilisation de l'authentification Windows de base ou intégrée). Remarque intéressante sur l'utilisation des informations d'identification et de l'en-tête Authorization : Le serveur ne doit pas accorder explicitement l'en-tête Authorization dans l'en-tête de réponse CORS Access-Control-Allow-Headers.

Remarquez qu'avec l'en-tête de réponse CORS Access-Control-Allow-Credentials, si le serveur émet cet en-tête, la valeur du caractère générique « * » ne peut alors pas être utilisée pour Access-Control-Allow-Origin. Au lieu de cela, la spécification CORS requiert l'utilisation de l'origine explicite. L'infrastructure de l'API Web gère tout cela pour vous, mais je mentionne cela ici, car vous pourrez remarquer ce comportement lors du débogage.

Cette discussion sur les informations d'identification et d'authentification prend une tournure intéressante. La description jusqu'à ce point concerne le scénario dans lequel le navigateur envoie implicitement les informations d'identification. Il est possible pour un client JavaScript d'envoyer explicitement les informations d'identification (à nouveau, habituellement via l'en-tête Authorization). Si c'est le cas, aucune des règles ou aucun des comportements mentionnés plus haut associés aux informations d'identification ne s'applique.

Pour ce scénario, le client définira explicitement l'en-tête Authorization sur la requête et ne devra pas définir withCredentials sur la XMLHttpRequest. Cet en-tête déclenchera une requête préliminaire et le serveur devra permettre l'en-tête Authorization avec l'en-tête de réponse CORS Access-Control-Allow-Headers. Le serveur ne devra pas non plus émettre l'en-tête de réponse CORS Access-Control-Allow-Credentials.

Voici à quoi devra ressembler le code client pour définir explicitement l'en-tête Authorization :

$.ajax({
  url: "http://localhost/WebApiCorsServer/Resources/1",
  headers: {
    "Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3Mi..."
  }
  // Other settings omitted
});

Voici la requête préliminaire :

OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Access-Control-Request-Method: GET
Origin: http://localhost:55912
Access-Control-Request-Headers: authorization
Accept: */*

Voici la réponse préliminaire :

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: authorization

Définir explicitement la valeur d'un jeton dans l'en-tête Authorization est une approche plus sûre de l'authentification car, vous évitez les éventuelles tentatives de falsification de requête intersites (CSRF, Cross Site Request Forgery). Vous pouvez voir cette approche dans les modèles d'applications à une seule page dans Visual Studio 2013.

Vous avez pris connaissance des principes fondamentaux de CORS au niveau HTTP, je vais vous montrer comment utiliser la nouvelle infrastructure CORS pour émettre ces en-têtes à partir de l'API Web.

Prise en charge de CORS dans l'API Web 2

La prise en charge de CORS dans l'API Web est une infrastructure complète permettant à une application de définir les autorisations pour les requêtes CORS. L'infrastructure tourne autour du concept d'une stratégie qui vous permet de spécifier les fonctionnalités CORS possibles pour une requête donnée dans l'application.

Tout d'abord, afin d'obtenir l'infrastructure CORS, vous devez référencer les bibliothèques CORS depuis l'application de l'API Web (elles ne sont pas référencées par défaut depuis l'un des modèles de l'API Web dans Visual Studio 2013). L'infrastructure CORS de l'API Web est disponible via NuGet comme package Microsoft.AspNet.WebApi.Cors. Si vous n'utilisez pas NuGet, il est également disponible comme partie de Visual Studio 2013, et vous devrez référencer deux assemblies : System.Web.Http.Cors.dll et System.Web.Cors.dll (sur ma machine, ils se trouvent dans C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages).

Ensuite, pour exprimer la stratégie, l'API Web fournit une classe d'attributs personnalisée appelée EnableCorsAttribute. Cette classe contient les propriétés pour les origines autorisées, les méthodes HTTP, les en-têtes de requête, les en-têtes de réponse et si les informations d'authentification sont autorisées (qui modèlent tous les détails de la spécification CORS mentionnés plus tôt).

Enfin, afin de permettre à l'infrastructure CORS de l'API Web de traiter les requêtes CORS et d'émettre les en-têtes de réponse CORS appropriés, elle doit examiner chaque requête dans l'application. L'API Web dispose d'un point d'extensibilité pour une telle interception via des gestionnaires de messages. De façon appropriée, l'infrastructure CORS de l'API Web implémente un gestionnaire de messages appelé CorsMessageHandler. Pour les requêtes CORS, elle consultera la stratégie exprimée dans l'attribut pour la méthode invoquée et émettra les en-têtes de réponse CORS appropriés.

EnableCorsAttribute La classe EnableCorsAttribute est la méthode d'expression d'une application de sa stratégie CORS. La classe EnableCorsAttribute possède un constructeur surchargé qui peut accepter trois ou quatre paramètres. Les paramètres (dans l'ordre) sont :

  1. Liste des origines autorisées
  2. Liste des en-têtes de requête autorisées
  3. Liste des méthodes HTTP autorisées
  4. Liste des en-têtes de réponse autorisées (facultatif)

Il existe également une propriété pour autoriser les informations d'identification (SupportsCredentials) et une autre pour spécifier la valeur de la durée du cache préliminaire (PreflightMaxAge).

La figure 2 montre un exemple d'application de l'attribut EnableCors pour les méthodes individuelles sur un contrôleur. Les valeurs utilisées pour les différents paramètres de stratégie CORS doivent correspondre aux requêtes et réponses CORS illustrées dans les exemples précédents.

Figure 2 Application de l'attribut EnableCors pour les méthodes d'action

public class ResourcesController : ApiController
{
  [EnableCors("http://localhost:55912", // Origin
              null,                     // Request headers
              "GET",                    // HTTP methods
              "bar",                    // Response headers
              SupportsCredentials=true  // Allow credentials
  )]
  public HttpResponseMessage Get(int id)
  {
    var resp = Request.CreateResponse(HttpStatusCode.NoContent);
    resp.Headers.Add("bar", "a bar value");
    return resp;
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "PUT",                          // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "POST",                         // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

Notez que chacun des paramètres du constructeur est une chaîne. Les valeurs multiples sont indiquées en spécifiant une liste séparée par des virgules (comme c'est le cas pour les en-têtes de requête dans la figure 2). Si vous souhaitez autoriser toutes les origines, tous les en-têtes de requête ou toutes les méthodes HTTP, vous pouvez utiliser un caractère générique « * » comme valeur (vous devez toujours être explicite pour les en-têtes de réponse).

Il est possible d'appliquer l'attribut EnableCors au niveau de la méthode, mais aussi au niveau de la classe ou globalement à l'application. Le niveau auquel l'attribut est appliqué configure CORS pour toutes les requêtes à ce niveau et en dessous dans le code de votre API Web. Ainsi, par exemple, si elle est appliquée au niveau de la méthode, la stratégie ne s'applique qu'aux requêtes pour cette action, alors que si elle est appliquée au niveau de la classe, la stratégie s'applique à toutes les requêtes à ce contrôleur. Enfin, si elle est appliquée globalement, la stratégie sera appliquée à toutes les requêtes.

Voici un autre exemple d'application de l'attribut au niveau de la classe. Les paramètres utilisés dans cet exemple sont assez permissifs, car le caractère générique est utilisé pour les origines, les en-têtes de requête et les méthodes HTTP autorisés :

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

S'il existe une stratégie dans plusieurs emplacements, l'attribut le « plus proche » est utilisé et les autres sont ignorés (la priorité est donc la méthode, puis la classe, et ensuite la globalité). Si vous avez appliqué la stratégie à un niveau plus élevé, mais que vous souhaitez ensuite exclure une requête à un niveau inférieur, vous pouvez utiliser une autre classe d'attributs appelée DisableCorsAttribute. En fait, cet attribut est une stratégie sans aucune autorisation attribuée.

Si vous disposez d'autres méthodes sur le contrôleur dans lequel vous ne voulez pas autoriser CORS, vous pouvez utiliser l'une des deux options. Tout d'abord, vous pouvez être explicite dans la liste de méthodes HTTP, comme illustré à la figure 3. Ou vous pouvez laisser le caractère générique, mais exclure la méthode Delete avec l'attribut DisableCors, comme illustré à la figure 4.

Figure 3 Utilisation des valeurs explicites pour les méthodes HTTP

[EnableCors("*", "*", "PUT, POST")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because DELETE is not in the method list above
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

Figure 4 Utilisation de l'attribut DisableCors

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because of the [DisableCors] attribute
  [DisableCors]
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

CorsMessageHandler Le CorsMessageHandler doit être activé pour que l'infrastructure CORS puisse accomplir sa mission d'interception des requêtes pour évaluer la stratégie CORS et émettre les en-têtes de réponse CORS. L'activation du gestionnaire de messages se fait généralement dans la classe de configuration de l'API Web de l'application en invoquant la méthode d'extension EnableCors :

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
  }
}

Si vous souhaitez fournir une stratégie CORS globale, vous pouvez passer une instance de la classe EnableCorsAttribute comme paramètre à la méthode EnableCors. Par exemple, le code suivant ajoutera une stratégie CORS permissive de façon globale au sein de l'application :

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
  }
}

Comme avec n'importe quel gestionnaire de messages, le CorsMessageHandler peut également être enregistré par route plutôt que globalement.

Donc, voilà, c'est tout pour l'infrastructure CORS « prête à l'emploi » de base dans l'API Web ASP.NET 2. L'un des avantages de l'infrastructure est qu'elle est extensible et permet davantage de scénarios dynamiques, ce que j'aborde maintenant.

Personnalisation de la stratégie

À partir des exemples précédents, il devrait être évident que la liste des origines (si le caractère générique n'est pas utilisé) est une liste statique compilée dans le code de l'API Web. Alors que cela peut fonctionner pendant le développement ou pour des scénarios spécifiques, cela ne suffit pas si la liste des origines (ou les autres autorisations) doit être déterminée de façon dynamique (disons, à partir d'une base de données).

Heureusement, l'infrastructure CORS dans l'API Web est extensible de façon à ce que la prise en charge d'une liste dynamique soit facile. En fait, l'infrastructure est si flexible qu'il existe deux approches générales pour personnaliser la génération de la stratégie.

Attribut de stratégie CORS personnalisé Une des approches possibles d'activation d'une stratégie CORS dynamique consiste à développer une classe d'attributs personnalisée qui peut générer la stratégie à partir d'une source de données. Cette classe d'attributs personnalisée peut-être utilisée à la place de la classe EnableCorsAttribute fournie par l'API Web. Cette approche est simple et donne l'assurance de pouvoir appliquer un attribut sur des classes et méthodes spécifiques (et de ne pas l'appliquer sur d'autres), selon les besoins.

Pour implémenter cette approche, vous créez un attribut personnalisé similaire à la classe EnableCorsAttribute existante. Le principal objectif est l'interface ICorsPolicyProvider, qui est responsable de la création de l'instance d'une CorsPolicy pour n'importe quelle requête donnée. La figure 5 contient un exemple.

Figure 5 Attribut de stratégie CORS personnalisé

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = false)]
public class EnableCorsForPaidCustomersAttribute :
  Attribute, ICorsPolicyProvider
{
  public async Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    if (await IsOriginFromAPaidCustomer(originRequested))
    {
      // Grant CORS request
      var policy = new CorsPolicy
      {
        AllowAnyHeader = true,
        AllowAnyMethod = true,
      };
      policy.Origins.Add(originRequested);
      return policy;
    }
    else
    {
      // Reject CORS request
      return null;
    }
  }
  private async Task<bool> IsOriginFromAPaidCustomer(
    string originRequested)
  {
    // Do database look up here to determine if origin should be allowed
    return true;
  }
}

La classe CorsPolicy possède toutes les propriétés pour exprimer les autorisations CORS à accorder. Les valeurs utilisées ici ne sont qu'un exemple, mais elles peuvent sans doute être définies de façon dynamique à partir d'une requête de base de données (ou de n'importe quelle autre source).

Fabrique de fournisseur de stratégie personnalisée La deuxième approche générale de création d'une stratégie CORS dynamique est de créer une fabrique de fournisseur de stratégie personnalisée. Il s'agit de l'élément de l'infrastructure CORS qui obtient le fournisseur de stratégie pour la requête actuelle. L'implémentation par défaut de l'API Web utilise les attributs personnalisés pour découvrir le fournisseur de stratégie (comme vous l'avez vu précédemment, la classe d'attributs elle-même était le fournisseur de stratégie). Il s'agit d'un autre élément enfichable de l'infrastructure CORS, et vous devrez implémenter votre propre fabrique de fournisseur de stratégie si vous voulez utiliser une approche pour la stratégie autre que les attributs personnalisés.

L'approche basée sur les attributs décrite précédemment fournit une association implicite d'une requête à une stratégie. L'approche d'une fabrique de fournisseur de stratégie personnalisée est différente de l'approche d'attributs, car elle requiert votre implémentation pour fournir la logique de correspondance de la requête entrante à la stratégie. Cette approche est plus grossière, comme il s'agit essentiellement d'une approche centralisée pour obtenir une stratégie CORS.

La figure 6 montre un exemple de ce à quoi pourrait ressembler une fabrique de fournisseur de stratégie personnalisée. Le principal objectif de cet exemple est l'implémentation de l'interface ICorsPolicyProviderFactory et de sa méthode GetCorsPolicyProvider.

Figure 6 Fabrique de fournisseur de stratégie personnalisée

public class DynamicPolicyProviderFactory : ICorsPolicyProviderFactory
{
  public ICorsPolicyProvider GetCorsPolicyProvider(
    HttpRequestMessage request)
  {
    var route = request.GetRouteData();
    var controller = (string)route.Values["controller"];
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    var policy = GetPolicyForControllerAndOrigin(
      controller, originRequested);
    return new CustomPolicyProvider(policy);
  }
  private CorsPolicy GetPolicyForControllerAndOrigin(
   string controller, string originRequested)
  {
    // Do database lookup to determine if the controller is allowed for
    // the origin and create CorsPolicy if it is (otherwise return null)
    var policy = new CorsPolicy();
    policy.Origins.Add(originRequested);
    policy.Methods.Add("GET");
    return policy;
  }
}
public class CustomPolicyProvider : ICorsPolicyProvider
{
  CorsPolicy policy;
  public CustomPolicyProvider(CorsPolicy policy)
  {
    this.policy = policy;
  }
  public Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    return Task.FromResult(this.policy);
  }
}

La différence principale dans cette approche est que seule l'implémentation doit déterminer la stratégie à partir de la requête entrante. Dans la figure 6, le contrôleur et l'origine peuvent être utilisés pour interroger une base de données afin d'obtenir les valeurs de stratégie. Une fois encore, cette approche est la plus souple, car elle requiert potentiellement plus d'efforts pour déterminer la stratégie à partir de la requête.

Pour utiliser la fabrique de fournisseur de stratégie personnalisée, vous devez l'enregistrer avec l'API Web via la méthode d'extension SetCorsPolicyProviderFactory dans la configuration de l'API Web :

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
    config.SetCorsPolicyProviderFactory(
      new DynamicPolicyProviderFactory());
  }
}

Contributions de la communauté en action

L'API Web ASP.NET est une infrastructure open source qui fait partie d'un ensemble plus important d'infrastructures open source collectivement appelé pile Web ASP.NET, ce qui inclut également MVC et les pages Web, entre autres.

Ces infrastructures sont utilisées pour créer la plateforme ASP.NET et sont organisées par l'équipe ASP.NET chez Microsoft. En tant qu'organisateur d'une plateforme open source, l'équipe ASP.NET apprécie les contributions de la communauté, et l'implémentation de CORS dans l'API Web est l'une de ces contributions.

Elle a été développée à l'origine par Brock Allen en tant que partie de la bibliothèque de sécurité thinktecture IdentityModel (thinktecture.github.io).

Débogage de CORS

Quelques techniques me viennent à l'esprit pour déboguer CORS si (et quand) vos appels AJAX d'origines croisées ne fonctionnent pas.

Côté client Une des approches possibles de débogage consiste à utiliser simplement le débogueur HTTP de votre choix (par exemple, Fiddler) et à inspecter toutes les requêtes HTTP. Avec la connaissance approfondie acquise plus tôt sur les détails de la spécification CORS, vous pouvez généralement comprendre pourquoi une requête AJAX en particulier ne se voit pas accorder l'autorisation d'inspecter les en-têtes HTPP CORS (ou leur absence).

Une autre approche consiste à utiliser les outils de développement F12 de votre navigateur. La fenêtre de la console dans les navigateurs modernes fournit un message d'erreur utile lorsque des appels AJAX échouent à cause de CORS.

Côté serveur L'infrastructure CORS fournit elle-même les messages de trace détaillés à l'aide des capacités de traçage de l'API Web. Tant que ITraceWriter est enregistré avec l'API Web, l'infrastructure CORS émettra des messages avec les informations sur le fournisseur de stratégie sélectionné, la stratégie utilisée et les en-têtes HTTP CORS émis. Pour plus d'informations sur le traçage de l'API Web, consultez la documentation de l'API Web sur MSDN.

Une fonctionnalité très demandée

CORS est une fonctionnalité très demandée depuis quelque temps, et est finalement intégrée à l'API Web. Cet article porte principalement sur les détails de CORS elle-même, mais ces connaissances sont essentielles pour l'implémentation et le débogage de CORS. Fort de ces connaissances, vous devrez pouvoir utiliser facilement la prise en charge de CORS dans l'API Web pour autoriser les appels d'origines croisées dans vos applications.

Brock Allen est consultant spécialisé dans le Microsoft .NET Framework, le développement Web et la sécurité Web. Il est également formateur pour l'organisme de formation DevelopMentor, consultant associé pour thinktecture GmbH & Co. KG, a participé aux projets open source de thinktecture et à la plateforme ASP.NET. Vous pouvez le contacter par l'intermédiaire de son site Web, brockallen.com, ou par courrier électronique à l'adresse brockallen@gmail.com.

Merci à l'expert technique suivant d'avoir relu cet article : Yao Huan Lin (Microsoft)
Yao Huang Lin (yaohuang@microsoft.com) est développeur de logiciels dans l'équipe de l'API Web ASP.NET chez Microsoft. Il a travaillé sur plusieurs composants du .NET Framework, y compris ASP.NET, Windows Communication Foundation (WCF) et Windows Workflow Foundation (WF).