ASP.NET-Web-API

CORS-Unterstützung in der ASP.NET-Web-API 2

Brock Allen

Cross-Origin Resource Sharing (CORS) ist eine World Wide Web Consortium(W3C)-Spezifikation (im Allgemeinen als Teil von HTML5 betrachtet), mit der JavaScript die von Browsern für die Sicherheit auferlegte Richtlinie für denselben Ursprung überwindet. Die Richtlinieneinschränkung für denselben Ursprung bedeutet, dass Ihr JavaScript nur AJAX-Aufrufe zurück an denselben Ursprung der Webseite, die den Code enthält, durchführen kann, wobei „Ursprung“ als die Kombination des Hostnamens, des Protokolls und der Portnummer definiert ist. Beispiel: JavaScript auf einer Webseite von „http://foo.com“ kann keine AJAX-Aufrufe an „http://bar.com“ (oder ebenso wenig an „http://www.foo.com“, „https://foo.com“ oder „http://foo.com:999“) durchführen.

CORS lockert diese Einschränkung dadurch, dass Server angeben können, welche Ursprünge Aufrufe an sie durchführen dürfen. CORS wird von Browsern umgesetzt, muss aber auf dem Server implementiert werden, und die neueste Version der ASP.NET-Web-API 2 unterstützt CORS vollständig. Mit der Web-API 2 können Sie eine Richtlinie konfigurieren, die den Zugriff auf Ihre APIs durch JavaScript-Clients von einem anderen Ursprung zulässt.

CORS-Grundlagen

Damit Sie die neuen CORS-Features in der Web-API verwenden können, sind Kenntnisse der Details von CORS selbst hilfreich, denn die Web-API-Implementierung entspricht der Spezifikation. Diese Details erscheinen jetzt möglicherweise penibel, tragen aber später zum Verständnis der verfügbaren Einstellungen in der Web-API bei – und wenn Sie CORS debuggen, können Sie Probleme anhand dieser Details schneller lösen.

Der grundlegende CORS-Mechanismus ist folgender: Wenn JavaScript versucht, einen ursprungsübergreifenden AJAX-Aufruf durchzuführen, sendet der Browser einen Header wie beispielsweise „Origin“ in der HTTP-Anforderung an den Server, um die Zulässigkeit des Aufrufs zu ermitteln. Der Server gibt an, was zulässig ist, indem er HTTP-Header wie beispielsweise „Access-Control-Allow-Origin“ in der Antwort zurückgibt. Diese Überprüfung der Berechtigung wird für jede spezielle URL durchgeführt, die der Client aufruft, wodurch unterschiedliche URLs verschiedene Berechtigungen haben können.

Zusätzlich zum Ursprung kann der Server durch CORS angeben, welche HTTP-Methoden zulässig sind, welche HTTP-Anfrageheader ein Client senden kann, welche HTTP-Antwortheader ein Client lesen kann, und ob der Browser automatisch Anmeldeinformationen (Cookies oder Autorisierungsheader) senden oder empfangen darf. Zusätzliche Anfrage- und Antwortheader geben an, welche dieser Features zulässig sind. Diese Header sind in Abbildung 1 zusammengefasst (wobei für einige der Features keine Header in der Anforderung, sondern nur in der Antwort gesendet werden).

Abbildung 1: CORS-HTTP-Header

Berechtigung/Feature Anforderungsheader Antwortheader
Ursprung Origin Access-Control-Allow-Origin
HTTP-Methode Access-Control-Request-Method Access-Control-Allow-Method
Anforderungsheader Access-Control-Request-Headers Access-Control-Allow-Headers
Antwortheader   Access-Control-Expose-Headers
Anmeldeinformationen   Access-Control-Allow-Credentials
Antwort mit Preflightcache   Access-Control-Max-Age

Die Browser können den Server auf zwei verschiedene Arten nach diesen Berechtigungen fragen: einfache CORS-Anforderungen und Preflight-CORS-Anforderungen.

Einfache CORS-Anforderungen Hier ein Beispiel für eine einfache CORS-Anforderung:

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

Es folgt die Antwort:

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}

Die Anforderung ist eine ursprungsübergreifende Anforderung von „http://localhost:55912“ an „http://localhost“, und der Browser fügt der Anforderung einen Origin-HTTP-Header hinzu, um den Ursprung des Aufrufs für den Server anzugeben. Der Server antwortet mit dem Antwortheader „Access-Control-Allow-Origin“, um anzugeben, dass dieser Ursprung zulässig ist. Der Browser setzt die Richtlinie des Servers um, und JavaScript empfängt den normalen Erfolgsrückruf.

Der Server kann entweder mit dem genauen Origin-Wert aus der Anforderung antworten, oder durch den Wert „*“ angeben, dass jeder Ursprung zulässig ist. Wenn der Server den Ursprung des Aufrufs nicht zulässt, ist der Access-Control-Allow-Origin-Header einfach nicht vorhanden, und für das aufrufende JavaScript erfolgt ein Fehlerrückruf.

Bei einer einfachen CORS-Anforderung erfolgt immer noch der Aufruf an den Server. Wenn Sie CORS erlernen, überrascht das vielleicht, aber dieses Verhalten unterscheidet sich nicht von einem Szenario, in dem der Browser ein <form>-Element erstellt hat und eine normale POST-Anforderung durchführt. CORS verhindert nicht, dass der Aufruf an den Server erfolgt, sondern stattdessen, dass das aufrufende JavaScript die Ergebnisse empfängt. Um zu verhindern, dass der Aufruf an den Server erfolgt, würden Sie eine Art von Autorisierung in den Servercode implementieren (vielleicht mit dem [Authorize]-Autorisierungsfilterattribut).

Das vorstehende Beispiel ist als einfache CORS-Anforderung bekannt, da der Typ des AJAX-Aufrufs vom Client entweder GET oder POST ist, als „Content-Type“ entweder „application/x-www-form-urlencoded“, „multipart/form-data“ oder „text/plain“ verwendet wird und keine zusätzlichen Anforderungsheader gesendet werden. Wenn der AJAX-Aufruf eine andere HTTP-Methode ist, „Content-Type“ einen anderen Wert enthält oder der Client zusätzliche Anforderungsheader senden möchte, dann wird die Anforderung als Preflightanforderung betrachtet. Preflightanforderungen funktionieren etwas anders.

Preflight-CORS-Anforderungen Wenn ein AJAX-Aufruf keine einfache Anforderung ist, erfordert er eine Preflight-CORS-Anforderung. Dabei handelt es sich einfach um eine zusätzliche HTTP-Anforderung an den Server, um die Berechtigung abzurufen. Diese Preflightanforderung wird automatisch vom Browser ausgeführt und verwendet die HTTP-Methode OPTIONS. Wenn der Server die Preflightanforderung erfolgreich beantwortet und die Berechtigung gewährt, führt der Browser den AJAX-Aufruf aus, den JavaScript durchführen möchte.

Wenn die Leistung von Bedeutung ist (und das ist sie fast immer), kann das Ergebnis dieser Preflightanforderung vom Browser zwischengespeichert werden. Dazu wird der Preflightantwort der Access-Control-Max-Age-Header hinzugefügt. Der Wert enthält die Dauer in Sekunden, für die die Berechtigungen zwischengespeichert werden können.

Hier ein Beispiel für eine Preflight-CORS-Anforderung:

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: */*

Und die Preflightantwort:

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

Hier ist die tatsächliche AJAX-Anforderung:

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}

Es folgt die AJAX-Antwort:

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}

In diesem Beispiel wird eine Preflight-CORS-Anforderung ausgelöst, weil die HTTP-Methode PUT verwendet wird, und der Client muss den Content-Type-Header senden, um anzugeben, dass die Anforderung den Inhaltstyp „application/json“ enthält. In der Preflightanforderung (zusätzlich zu „Origin“) dienen die Anforderungsheader „Access-Control-Request-Method“ und „Access-Control-Request-Headers“ dazu, die Berechtigung für den Typ der HTTP-Methode und den zusätzlichen Header, den der Client senden möchte, einzuholen.

Der Server hat die Berechtigung gewährt (und die Dauer des Preflightcaches festgelegt), und anschließend hat der Browser den AJAX-Aufruf zugelassen. Wenn der Fall eintritt, dass der Server keines der angeforderten Features genehmigt, fehlt der entsprechende Antwortheader, der AJAX-Aufruf findet nicht statt, und stattdessen erfolgt der JavaScript-Fehlerrückruf.

Die obigen HTTP-Anforderungen und -Antworten wurden unter Verwendung von Firefox durchgeführt. Bei Verwendung von Internet Explorer würden Sie bemerken, dass ein zusätzlicher Accept-Header angefordert wird. Wenn Sie Chrome verwenden, stellen Sie zusätzliche Anforderungen sowohl von „Accept“ als auch von „Origin“ fest. Interessanterweise werden Sie „Accept“ oder „Origin“ nicht in den „Access-Control-Allow-Headers“ sehen, da sie gemäß Spezifikation enthalten sind und ausgelassen werden können (die Web-API befolgt dies). Es wird diskutiert, ob „Origin“ und „Accept“ tatsächlich angefordert werden müssen, aber unter Berücksichtigung der heutigen Funktionsweise dieser Browser ist es sehr wahrscheinlich, dass Ihre CORS-Richtlinie der Web-API die Anforderungen enthalten muss. Die Browseranbieter legen die Spezifikation anscheinend leider verschieden aus.

Antwortheader Mit dem Antwortheader „Access-Control-Expose-Headers“ können Sie einem Client ganz einfach Zugriff auf Antwortheader gewähren. Hier ein Beispiel für eine HTTP-Antwort, die dem aufrufenden JavaScript die Genehmigung für den Zugriff auf den benutzerdefinierten Antwortheader „bar“ erteilt:

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}

Der JavaScript-Client kann einfach die XMLHttpRequest-getResponseHeader-Funktion verwenden, um den Wert zu lesen. Hier ist ein Beispiel mit jQuery:

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

Anmeldeinformationen und Authentifizierung Zum vielleicht verwirrendsten Aspekt von CORS gehören Anmeldeinformationen und Authentifizierung. Im Allgemeinen kann die Authentifizierung mit Web-APIs entweder durch ein Cookie oder durch einen Authorization-Header durchgeführt werden (es gibt weitere Möglichkeiten, aber diese beiden sind die häufigsten). Wenn eins von beiden zuvor eingerichtet wurde, übergibt bei normaler Browseraktivität der Browser diese Werte bei nachfolgenden Anforderungen implizit an den Server. Bei ursprungsübergreifendem AJAX muss diese implizite Wertübergabe allerdings explizit in JavaScript angefordert werden (über das withCredentials-Flag in „XMLHttpRequest“), und sie muss in der CORS-Richtlinie des Servers explizit zugelassen werden (über den Antwortheader „Access-Control-Allow-Credentials“).

Im folgenden Beispiel für einen JavaScript-Client wird das withCredentials-Flag mit jQuery festgelegt:

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

Das withCredentials-Flag führt zwei Aufgaben aus: Wenn der Server ein Cookie ausgibt, kann der Browser es annehmen, und wenn der Browser ein Cookie hat, kann er es an den Server senden.

Im Folgenden finden Sie ein Beispiel für die HTTP-Antwort zur Genehmigung von Anmeldeinformationen:

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

Der Antwortheader „Access-Control-Allow-Credentials“ führt zwei Aufgaben aus: Wenn die Antwort ein Cookie enthält, kann der Browser es annehmen, und wenn der Browser in der Anforderung ein Cookie gesendet hat, kann der JavaScript-Client die Ergebnisse des Aufrufs empfangen. Mit anderen Worten, wenn der Client „withCredentials“ festlegt, sieht er nur einen Erfolgsrückruf im JavaScript, wenn der Server (in der Antwort) Anmeldeinformationen zulässt. Wurde „withCredentials“ festgelegt und der Server lässt keine Anmeldeinformationen zu, erhält der Client keinen Zugriff auf die Ergebnisse, und der Clientfehlerrückruf erfolgt.

Wenn anstelle von Cookies der Authorization-Header verwendet wird (z. B. bei Verwendung der Standard- oder der integrierten Windows-Authentifizierung), ist derselbe Satz von Regeln und Verhaltensweisen gültig. Eine interessante Anmerkung zur Verwendung von Anmeldeinformationen und dem Authorization-Header: Es ist nicht erforderlich, dass der Server den Authorization-Header im CORS-Antwortheader „Access-Control-Allow-Headers“ explizit zulässt.

Beachten Sie beim CORS-Antwortheader „Access-Control-Allow-Credentials“, dass bei Ausgabe dieses Headers durch den Server der Platzhalterwert „*“ nicht für „Access-Control-Allow-Origin“ verwendet werden kann. Stattdessen fordert die CORS-Spezifikation die Verwendung des expliziten Ursprungs. Das Web-API-Framework übernimmt all dies für Sie, aber ich erwähne es, da Sie dieses Verhalten möglicherweise beim Debuggen feststellen.

Bei dieser Diskussion von Anmeldeinformationen und Authentifizierung gibt es eine interessante Besonderheit. Bis zu diesem Punkt betraf die Beschreibung das Szenario, in dem der Browser implizit Anmeldeinformationen sendet. Ein JavaScript-Client hat die Möglichkeit, Anmeldeinformationen explizit zu senden (auch hier normalerweise über den Authorization-Header). Wenn dies der Fall ist, gelten keine der zuvor erwähnten Regeln oder Verhaltensweisen, die die Anmeldeinformationen betreffen.

In diesen Szenarien legt der Client den Authorization-Header explizit in der Anforderung fest, und er muss das withCredentials-Flag in „XMLHttpRequest“ nicht festlegen. Der Header löst eine Preflightanforderung aus, und der Server muss den Authorization-Header mit dem CORS-Antwortheader „Access-Control-Allow-Headers“ zulassen. Außerdem muss der Server nicht den CORS-Antwortheader „Access-Control-Allow-Credentials“ ausgeben.

Hier ein Beispiel für diesen Clientcode, mit dem der Authorization-Header explizit festgelegt wird:

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

Hier ist die Preflightanforderung:

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: */*

Hier ist die Preflightantwort:

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

Die explizite Einrichtung eines Tokenwerts im Authorization-Header bietet mehr Sicherheit bei der Authentifizierung, da Sie die Möglichkeit von websiteübergreifenden Anforderungsfälschungen (Cross-Site Request Forgery, CSRF) vermeiden. Sie können diesen Ansatz in den neuen Visual Studio 2013-Vorlagen „Single-Page Application“ (SPA) sehen.

Sie haben jetzt die Grundlagen von CORS auf HTTP-Ebene kennengelernt, und ich möchte Ihnen zeigen, wie Sie das neue CORS-Framework nutzen, um diese Header aus der Web-API auszugeben.

CORS-Unterstützung in der Web-API 2

Die CORS-Unterstützung in der Web-API ist ein vollständiges Framework, mit dem eine Anwendung die Berechtigungen für CORS-Anforderungen definieren kann. Im Mittelpunkt des Frameworks steht das Konzept einer Richtlinie, mit der Sie angeben können, dass die CORS-Features für jede gegebene Anforderung an die Anwendung zulässig sind.

Um das CORS-Framework zu erhalten, müssen Sie zuerst die CORS-Bibliotheken in Ihrer Web-API-Anwendung referenzieren (die Web-API-Vorlagen in Visual Studio 2013 enthalten standardmäßig keinen entsprechenden Verweis). Das CORS-Framework für die Web-API ist über NuGet als Microsoft.AspNet.WebApi.Cors-Paket verfügbar. Wenn Sie NuGet nicht verwenden, ist es auch als Teil von Visual Studio 2013 verfügbar, und Sie müssen zwei Assemblys referenzieren: „System.Web.Http.Cors.dll“ und „System.Web.Cors.dll“ (auf meinem Computer befinden sich diese unter „C:\Programme (x86)\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages“).

Um als Nächstes die Richtlinie auszudrücken, stellt die Web-API eine benutzerdefinierte Attributklasse namens „EnableCorsAttribute“ bereit. Diese Klasse enthält Eigenschaften für die zulässigen Ursprünge, HTTP-Methoden, Anforderungsheader, Antwortheader und die Zulässigkeit von Anmeldeinformationen (die alle zuvor erläuterten Details der CORS-Spezifikation modellieren).

Damit das CORS-Framework für die Web-API die CORS-Anforderungen verarbeitet und die entsprechenden CORS-Antwortheader ausgibt, muss das Framework zum Schluss jede Anforderung an die Anwendung betrachten. Die Web-API verfügt über einen Erweiterungspunkt für ein solches Abfangen über Meldungshandler. Das CORS-Framework für die Web-API implementiert entsprechend einen Meldungshandler namens „CorsMessageHandler“. Für die CORS-Anforderungen berücksichtigt der Meldungshandler die Richtlinie, die im Attribut für die aufgerufene Methode ausgedrückt wird, und gibt die geeigneten CORS-Antworthandler aus.

„EnableCorsAttribute“ Mit der EnableCorsAttribute-Klasse kann eine Anwendung ihre CORS-Richtlinie ausdrücken. Die EnableCorsAttribute-Klasse hat einen überladenen Konstruktor, der entweder drei oder vier Parameter akzeptieren kann. Die Parameter sind (geordnet):

  1. Liste der zulässigen Ursprünge
  2. Liste der zulässigen Anfrageheader
  3. Liste der zulässigen HTTP-Methoden
  4. Liste der zulässigen Antwortheader (optional)

Es gibt auch eine Eigenschaft für die Zulässigkeit von Anmeldeinformationen („SupportsCredentials“) und eine weitere zum Festlegen des Werts für die Dauer des Preflightcaches („PreflightMaxAge“).

Abbildung 2 enthält ein Beispiel für die Anwendung des EnableCors-Attributs auf einzelne Methoden in einem Controller. Die Werte, die für die verschiedenen CORS-Richtlinieneinstellungen verwendet werden, sollten den CORS-Anforderungen und -Antworten entsprechen, die in den vorherigen Beispielen gezeigt wurden.

Abbildung 2: Anwenden des EnableCors-Attributs auf Aktionsmethoden

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

Jeder Konstruktorparameter ist eine Zeichenfolge. Mehrere Werte werden durch die Angabe einer kommagetrennten Liste aufgeführt (wie in Abbildung 2 für die zulässigen Anforderungsheader angegeben). Wenn Sie alle Ursprünge, Anforderungsheader oder HTTP-Methoden zulassen möchten, können Sie ein „*“ als Wert verwenden (für Antwortheader ist dennoch eine ausdrückliche Angabe notwendig).

Zusätzlich zur Anwendung des EnableCors-Attributs auf Methodenebene können Sie es auch auf Klassenebene oder global auf die Anwendung anwenden. Die Ebene, auf die das Attribut angewendet wird, konfiguriert CORS für alle Anforderungen auf dieser Ebene und darunter in Ihrem Web-API-Code. Wenn das Attribut also zum Beispiel auf Methodenebene angewendet wird, gilt die Richtlinie nur für Anforderungen für diese Aktion. Bei einer Anwendung auf Klassenebene hingegen ist die Richtlinie für alle Anforderungen an diesen Controller gültig. Bei einer globalen Anwendung schließlich gilt die Richtlinie für alle Anforderungen.

Nachstehend ein weiteres Beispiel für die Anwendung des Attributs auf Klassenebene. Die Einstellungen in diesem Beispiel sind recht großzügig, denn für die zulässigen Ursprünge, Anforderungsheader und HTTP-Methoden wird der Platzhalter verwendet:

[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);
  }
}

Wenn es an mehreren Stellen eine Richtlinie gibt, wird das Attribut verwendet, das am nächsten ist, und die anderen ignoriert (die Rangfolge ist demnach Methode, dann Klasse, dann global). Wenn Sie die Richtlinie auf einer höheren Ebene angewendet haben, aber eine Anforderung auf einer niedrigeren Ebene ausschließen möchten, verwenden Sie eine weitere Attributklasse, „DisableCorsAttribute“. Dieses Attribut ist im Grunde eine Richtlinie ohne zugelassene Berechtigungen.

Wenn Sie andere Methoden im Controller verwenden, bei denen Sie CORS nicht zulassen möchten, können Sie eine von zwei Optionen nutzen. Erstens können Sie die HTTP-Methoden ausdrücklich in einer Liste angeben, wie in Abbildung 3 gezeigt. Oder Sie können den Platzhalter lassen, dabei aber die Delete-Methode durch das DisableCors-Attribut ausschließen, wie in Abbildung 4 gezeigt.

Abbildung 3: Verwenden ausdrücklicher Werte für HTTP-Methoden

[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);
  }
}

Abbildung 4: Verwenden des DisableCors-Attributs

[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“: Eine Aufgabe des CORS-Frameworks besteht darin, Anforderungen abzufangen, um die CORS-Richtlinie zu evaluieren und die CORS-Antwortheader auszugeben. Für diese Aufgabe muss „CorsMessageHandler“ aktiviert sein. Die Aktivierung des Meldungshandlers erfolgt normalerweise in der Web-API-Konfigurationsklasse der Anwendung durch den Aufruf der EnableCors-Erweiterungsmethode:

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

Wenn Sie eine globale CORS-Richtlinie bereitstellen möchten, übergeben Sie eine Instanz der EnableCorsAttribute-Klasse als Parameter an die EnableCors-Methode. Der folgende Code würde zum Beispiel eine großzügige CORS-Richtlinie global in der Anwendung konfigurieren:

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

Wie jeder Meldungshandler kann „CorsMessageHandler“ alternativ pro Route anstatt global registriert werden.

Soweit also zum grundlegenden standardmäßigen CORS-Framework in der ASP.NET-Web-API 2. Eine schöne Eigenschaft des Frameworks ist seine Erweiterbarkeit für dynamischere Szenarien, und auf dieses Thema möchte ich als Nächstes eingehen.

Anpassen der Richtlinie

Aus den vorherigen Beispielen geht recht deutlich hervor, dass die Liste der Ursprünge (wenn der Platzhalter nicht verwendet wird) eine statische, in den Web-API-Code kompilierte Liste ist. Dies funktioniert möglicherweise während der Entwicklung oder für bestimmte Szenarien, reicht aber nicht aus, wenn die Liste der Ursprünge (oder andere Berechtigungen) dynamisch bestimmt werden muss (beispielsweise aus einer Datenbank).

Glücklicherweise ist das CORS-Framework in der Web-API so erweiterbar, dass die Unterstützung einer dynamischen Liste von Ursprüngen einfach ist. Das Framework ist sogar so flexibel, dass es zwei allgemeine Ansätze zum Anpassen der Generierung der Richtlinie gibt.

Benutzerdefiniertes CORS-Richtlinienattribut Eine Option zur Aktivierung einer dynamischen CORS-Richtlinie ist die Entwicklung einer benutzerdefinierten Attributklasse, die die Richtlinie aus einer Datenquelle generieren kann. Diese benutzerdefinierte Attributklasse wird anstelle der von der Web-API bereitgestellten EnableCorsAttribute-Klasse verwendet. Dieser Ansatz ist einfach und bewahrt die Fähigkeit zur Feinabstimmung, d. h. ein Attribut nach Bedarf auf bestimmte Klassen und Methoden anzuwenden (und nicht auf andere).

Um den Ansatz zu implementieren, erstellen Sie einfach ein benutzerdefiniertes Attribut ähnlich der vorhandenen EnableCorsAttribute-Klasse. Das Hauptaugenmerk liegt auf der ICorsPolicyProvider-Schnittstelle, die dafür verantwortlich ist, eine Instanz von „CorsPolicy“ für jede gegebene Anforderung zu erstellen. Abbildung 5 enthält ein Beispiel.

Abbildung 5: Ein benutzerdefiniertes CORS-Richtlinienattribut

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

Die CorsPolicy-Klasse hat alle Eigenschaften, um die CORS-Berechtigungen auszudrücken, die gewährt werden sollen. Die hier verwendeten Werte sind nur ein Beispiel, könnten aber sicherlich durch die Abfrage einer Datenbank (oder einer beliebigen anderen Quelle) dynamisch aufgefüllt werden.

Benutzerdefinierte Richtlinienanbieterfactory Der zweite allgemeine Ansatz, um eine dynamische CORS-Richtlinie zu erhalten, ist die Erstellung einer benutzerdefinierten Richtlinienanbieterfactory. Dies ist der Teil vom CORS-Framework, der den Richtlinienanbieter für die aktuelle Anforderung abruft. Die Standardimplementierung der Web-API verwendet die benutzerdefinierten Attribute, um den Richtlinienanbieter zu ermitteln (wie Sie vorhin gesehen haben, war die Attributklasse selbst der Richtlinienanbieter). Dies ist ein weiterer austauschbarer Bestandteil vom CORS-Framework. Wenn Sie anstelle der benutzerdefinierten Attribute eine andere Option für die Richtlinie nutzen möchten, dann implementieren Sie eine eigene Richtlinienanbieterfactory.

Der oben beschriebene attributbasierte Ansatz stellt eine implizite Zuordnung einer Anforderung zu einer Richtlinie bereit. Davon unterscheidet sich der Ansatz mit einer benutzerdefinierten Richtlinienanbieterfactory, denn Ihre Implementierung muss die Logik bereitstellen, um die eingehende Anforderung einer Richtlinie zuzuordnen. Dieser Ansatz ist grobkörniger, da er im Wesentlichen ein zentralisierter Ansatz zum Abrufen einer CORS-Richtlinie ist.

Abbildung 6 zeigt ein Beispiel dafür, wie eine benutzerdefinierte Richtlinienanbieterfactory aussehen kann. Der Hauptschwerpunkt in diesem Beispiel ist die Implementierung der ICorsPolicyProviderFactory-Schnittstelle und deren GetCorsPolicyProvider-Methode.

Abbildung 6: Eine benutzerdefinierte Richtlinienanbieterfactory

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

Der wesentliche Unterschied in diesem Ansatz ist, dass es vollständig der Implementierung überlassen bleibt, anhand der eingehenden Anforderung die Richtlinie zu bestimmen. Im Code in Abbildung 6 können der Controller und der Ursprung verwendet werden, um Richtlinienwerte aus einer Datenbank abzufragen. Wieder ist dieser Ansatz der flexibelste, erfordert aber ggf. mehr Arbeit, um die Richtlinie anhand der Anforderung zu bestimmen.

Um die benutzerdefinierte Richtlinienanbieterfactory zu verwenden, müssen Sie sie bei der Web-API registrieren. Führen Sie die Registrierung über die SetCorsPolicyProviderFactory-Erweiterungsmethode in der Web-API-Konfiguration aus:

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

Communitybeiträge in Aktion

Die ASP.NET-Web-API ist ein Open Source-Framework und Teil einer größeren Gruppe von Open Source-Frameworks, die zusammen als ASP.NET-Webstapel bezeichnet werden. Dazu gehören u. a. MVC und Web Pages.

Diese Frameworks werden zur Erstellung der ASP.NET-Plattform verwendet und führend vom ASP.NET-Team bei Microsoft betreut. Als Kurator einer Open Source-Plattform begrüßt das ASP.NET-Team Beiträge der Community, und die CORS-Implementierung in der Web-API ist ein solcher Beitrag.

CORS wurde ursprünglich von Brock Allen als Teil der IdentityModel-Sicherheitsbibliothek von thinktecture entwickelt (thinktecture.github.io).

Debuggen von CORS

Es kommen einige Techniken zum Debuggen von CORS in Betracht, wenn Ihre AJAX-Aufrufe nicht funktionieren.

Clientseite Eine Option fürs Debuggen ist, einfach den HTTP-Debugger Ihrer Wahl zu nutzen (zum Beispiel Fiddler) und alle HTTP-Anforderungen zu prüfen. Mit den Kenntnissen, die Sie nun bereits über die Details der CORS-Spezifikation gewonnen haben, können Sie normalerweise durch die Prüfung der CORS-HTTP-Header (oder deren Fehlen) ermitteln, warum einem bestimmten AJAX-Aufruf keine Berechtigung gewährt wird.

Eine weitere Option ist, die F12-Entwicklertools Ihres Browser zu verwenden. Das Konsolenfenster in modernen Browsern stellt eine nützliche Fehlermeldung bereit, wenn ein AJAX-Aufruf aufgrund von CORS fehlschlägt.

Serverseite Das CORS-Framework selbst stellt detaillierte Ablaufverfolgungsmeldungen bereit, wozu es die Ablaufverfolgungsfunktionen der Web-API verwendet. Solange „ITraceWriter“ in der Web-API registriert ist, gibt das CORS-Framework Meldungen aus, die Informationen über den ausgewählten Richtlinienanbieter, die verwendete Richtlinie und die ausgegebenen CORS-HTTP-Header enthalten. Weitere Informationen über die Web-API-Ablaufverfolgung finden Sie in der Web-API-Dokumentation auf MSDN.

Ein Feature mit starker Nachfrage

CORS wurde seit einiger Zeit stark nachgefragt und nun endlich in die Web-API integriert. Dieser Artikel beschäftigt sich viel mit den Details von CORS selbst, aber dieses Wissen ist von entscheidender Bedeutung, um CORS zu implementieren und zu debuggen. Mit diesen Kenntnissen ausgerüstet, können Sie die CORS-Unterstützung in der Web-API nutzen, um ursprungsübergreifende Aufrufe in Ihren Anwendungen zuzulassen.

Brock Allen ist als Berater tätig und hat sich auf Microsoft .NET Framework, Webentwicklung und webbasierte Sicherheit spezialisiert. Er arbeitet außerdem als Kursleiter für das Schulungsunternehmen DevelopMentor und als Associate Consultant für thinktecture GmbH & Co. KG. Allen wirkt an Open Source-Projekten von thinktecture sowie an der ASP.NET-Plattform mit. Sie können ihn über seine Website brockallen.com erreichen oder ihm unter brockallen@gmail.com eine E-Mail senden.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Yao Huan Lin (Microsoft)
Yao Huang Lin (yaohuang@microsoft.com) ist Softwareentwickler im Team für die ASP.NET-Web-API bei Microsoft. Er hat an vielen .NET Framework-Komponenten mitgearbeitet, darunter ASP.NET, Windows Communication Foundation (WCF) und Windows Workflow Foundation (WF).