Wer Fullstack-Anwendungen entwickelt, kommt mit hoher Wahrscheinlichkeit nicht um das Thema CORS herum. Wenn man das Thema versteht, dann ist es im Grunde sehr einfach; nur leider sind viele Anleitungen zu diesem Thema übermäßig kompliziert und gehen sehr ins Detail. Dadurch sind diese Anleitungen nicht gerade einsteigerfreundlich. Aus diesem Grund habe ich diesen Artikel geschrieben.

Warum man einen CORS Fehler bekommt

Eingangs wurde die These aufgestellt, dass ein Fullstack-Entwickler mit hoher Wahrscheinlichkeit nicht um das Thema CORS herumkommt. Grund dafür ist die Tatsache, dass es in der Regel bei der Kommunikation zwischen Frontend und Backend zu einem CORS Error kommt. Der Grund dafür ist, dass Browser einen eingebauten Sicherheitsmechanismus namens Same-Origin-Policy besitzen. Dieser Sicherheitsmechanismus soll Cross-Site-Request-Forgery (CSRF) Exploits verhindern. An dieser Stelle soll der Einfachheit halber nicht weiter auf diese Exploits eingegangen werden. Eine Origin ist eine URL mit Protokoll, Hostname und Port. Von dort wird Webcontent geladen. Weil der Webcontent von dort kommt, ist es die Herkunft, also Origin. Wenn ein JavaScript-Skript von einer Origin, beispielsweise http://localhost:4200/script.js geladen wird, dann darf dieses Skript nur auf Inhalte von der gleichen Origin http://localhost:4200 zugreifen. Möchte das Skript dynamisch eine Datei von http://localhost:8080 laden, dann kommt es clientseitig zu einem CORS-Fehler, denn der Port ist nicht der gleiche. Origins sind nämlich nur gleich, wenn Protokoll, Hostname und Port genau gleich sind.

Wenn nun das Frontend die URL http://localhost:4200 besitzt und das Backend die URL http://localhost:8080, wie dies beispielsweise bei einem Spring Boot / Angular Tech Stack der Fall ist, dann kann das Frontend keine Responses des Backends entgegennehmen, weil der Port nicht der gleiche ist, und damit die Origin eine andere ist.

Damit nun das Frontend aber trotzdem mit dem Backend kommunizieren kann, muss das Backend den Response Header Access-Control-Allow-Origin auf die URL des Frontends setzen. In diesem Fall also auf http://localhost:4200. Dieser Response Header wird allerdings nicht in dem Response des eigentlichen Requests abgeschickt. Stattdessen wird vor dem eigentlichen Request ein weiterer sogenannter Preflight-Request vom Frontend abgeschickt. Dieser Preflight-Request ist nicht vom Typ POST oder GET, sondern vom Typ OPTIONS. In der Regel wird dieser Preflight-Request automatisch abgeschickt, sodass sich der Entwickler darum gar nicht kümmern muss. Wichtig ist also nur, dafür zu sorgen, dass das Backend den Access-Control-Allow-Origin Header setzt. In dem Kapitel Wie funktioniert das in der Praxis? Wird erläutert, wie der Header in einem Spring Boot Backend gesetzt werden kann und wie die Kommunikation mit einem Angular-Frontend sichergestellt werden kann.

Neben dem Access-Control-Allow-Origin Header gibt es übrigens noch weitere Header mit denen die Kommunikation erlaubt oder eingeschränkt werden kann. Der Vollständigkeit sollen diese an dieser Stelle aufgelistet werden, aber der Einfachheit halber, soll nicht weiter auf sie eingegangen werden. Es gibt neben dem Access-Control-Allow-Origin Header noch die Header Access-Control-Allow-Headers, Access-Control-Allow-Credentials, Access-Control-Expose-Headers, Access-Control-Max-Age und Access-Control-Allow-Methods.

Wie man einen CORS Fehler in der Praxis verhindert

Wenn man dafür sorgen möchte, dass ein Spring Boot Backend mit einem Angular-Frontend kommunizieren kann, dann gibt es dafür zwei Möglichkeiten. Zum einen kann eine Origin global für jeden Request erlaubt werden, indem man eine WebConfigurer-Bean definiert und die addCorsMapping-Methode überschreibt. In Abbildung 1 wird jeder Zugriff auf die Endpunkte /api/v1/** erlaubt, wenn die Origin http://localhost:4200 entspricht.

Abbildung 1

Außerdem besteht die Möglichkeit mit Hilfe der @CrossOrigin-Annotation eine Origin für einen spezifischen Controller oder einen spezifischen Endpunkt zu erlauben. Abbildung 2 zeigt, wie dies aussehen kann.

Abbildung 2