Was bedeutet CORS? Wie löse ich den Fehler?
Du möchtest verstehen, warum CORS entsteht und wie ich diesen verhindern kann?
Der Post garantiert, dass Du Deinen CORS beheben kannst.
Starten wir!
Ziel: CORS-Fehler verstehen + lösen
Wenn Du schon etwas mit Angular / Ionic gearbeitet hast, arbeitest Du Angulars HTTPModule. Die Anfänger frustriert der CORS-Fehler.
Heute will ich Dir zeigen, warum der CORS-Fehler entsteht und wie Du den CORS Fehler kurzfristig und dauerhaft umgehen kannst.
Die beakannte Webframeworks wie Race, Vue, Angular oder Ionic nutzen JavaScripts XMLHttpRequest als technische Grundlage, um entfernte APIs mit GET und POST abzurufen. XMLHttpRequest vermeidet das Neuladen der Tabs. Bei PHP ist das Neuladen zwingend notwendig, weil der Server die Daten für den Client vorbereitet (Server-seitiges Rendering).
Mehr Lesestoff: Alles zu GET / Post RFC
XMLHttpRequest cannot load https://api.example.com. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.
Ein Framework wie Angular / Ionic vereinfacht auf den ersten Blick die Nutzung des umständlichen JavaScript XMLHttpRequest. Die Wirklichkeit sieht anders aus.
Konzept: SOP, CORS, GET + POST
Ich zeige Dir, wie ein typischer GET- und POST-Anfrage abläuft und welche „Regeln“ Dein Browser ständig überprüfen muss.
Um den Fehler zu verstehen, möchte ich isoliert GET– und POST-Abfragen auf Basis des Angular / Ionic Framework entwickeln. Wir bauen ein simples Userinterface mit zwei Buttons, um einen GET- und POST-Anfrage absenden zu können.
Ein Textfeld benötigt die Benutzeroberfläche unseres CORS-Demo-Projekts, um die Inhalte für die POST-Anfrage zu einzugeben.
Ein Angular-Provider / Service stellt das Angular Modul als Schnittstelle zum Internet dar.
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.
Grundlagen: Unterschiedliche Auflösungen. Gleiche App.
Bevor ich CORS erklären kann, möchte ich Dir die Same-Orign-Policy (SOP) näher bringen, weil diese mit dem CORS-Fehler unmittelbar zusammenhängt.
Unser Web soll sicher bleiben.
Die Same-Origin Policy (SOP) garantiert, dass der Browser ein Webressource wie eine JavaScript- oder CSS-Dokument NUR von der GLEICHEN Domain laden darf.
Diese Regel unterbindet, dass ein Hacker mit XSS bösartigen Code über Deine Webseite auf den Geräten der Webseitennutzer ausführen kann.
In der Vergangenheit konnten Hacker bei Google Adsenes Werbung einkaufen. In der Werbung konnten sie JavaScript oder das veraltet Adobe Flash ausführen und Code von anderen Domänen laden. Der Code vom NICHT-GLEICHEN-URSPRUNG installierte Malware innerhalb vom Browsers oder hat Schadprogramme auf den Computer heruntergeladen.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://lippke.li/. (Reason: CORS request did not succeed).
Corss-Origin Resource Sharing (CORS) hilft dabei, die Sperre von Same-Origin Policy (SOP) zu umgehen. Dann kann Dein Browser auf die Script- und CSS-Dokumenten auf anderen Domains zugreifen.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://lippke.li/. (Reason: CORS header 'Access-Control-Allow-Origin' does not match 'http://localhost:8100/').
Wer hätte’s gedacht, CORS ist gut! – Wäre nicht die Anfänger unfreundliche Verwendung mit Angular.
Eine SIMPLE, grafische Erklärung von CORS und SOP
Wir gehen mal davon aus, dass Du eine neue fanzy Schriftart für Deine App / Webseite brauchst.
Um die kostenlose Schriftart von Google in Deine App einzubinden, musst Du eine HTTP-GET-Anfrage an den Google Server senden.
<link href="https://fonts.googleapis.com/css?family=Roboto&display=swap" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
</style>
Folgende Bilderfolge zeigen (stark vereinfacht), wie die Datenströme aussehen könnten.
Deine Webseite fragt mit GET nach und erhält ein .ttf Schriftart-Datei als Antwort zurück. Leider ist das nicht so einfach, weil die Same-Origin-Policy (SOP) das verbietet.
In der Realität läuft es aber etwas anderes ab.
Auf den ersten Blick sieht es so aus, dass wir eine GET-Abfrage absenden können, aber keine Antwort von Google erhalten.
Bei Dir taucht nicht mal eine 40X oder 50X Fehler auf, sondern folgender Warnung erscheint in Deiner Web-Konsole.
"Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at $somesite" message: "Http failure response for https://lippke.li: 0 Unknown Error" name: "HttpErrorResponse" ok: false status: 0 statusText: "Unknown Error"
Bevor der Browser eine GET-Abfrage sendet, geht dem GET- eine OPTIONS-Anfrage zu vor.
Die OPTIONS-Anfrage ist wie ein Aufklärer / Späher im Krieg.
Die OPTIONS-Anfrage befragt den Server, welche Anfrage-Methoden der Server unterstützt. Die bekanntesten Abfragemethoden sind GET, POST, HEAD, PUT und DELETE.
Der Server antwortet zur Überprüfung von CORS; ob dieser die Allow-Orign-*-Header gesetzt sind. Falls diese nicht gesetzt sind, versucht Dein Browser es nicht einmal, die GET-Anfrage abzusenden.
Blöd.
Coden: LÖSUNG FÜR CORS
Zur Beruhigung: Ich habe für Dich 3 Lösungswege im Angebot.
Überlege zu nächst, ob Du den Backendtyp(in) kennst oder nicht …
Hast Du diese Frage geklärt, können wir EINEN, der drei Lösungsmöglichkeiten umsetzen.
CORS ist fast zu 100 % Backend-Programmierfehler (solange Du im Frontend nicht total unfähig bist).
Ich zeige Dir, wie Du die drei Lösungsansätze umsetzen kannst.
Fall #1: Du hast Kontrolle über das Backend
Setzte die CORS-Header mit PHP. Hier ein Beispiel:
function addCors() {
// Teste ob die URL mit anderer Domain gestezt sist
if (isset($_SERVER['HTTP_ORIGIN'])) {
// Wichtige CORS header, um GET und POST zu erlauben
header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Max-Age: 86400'); // cache for 1 day
}
// Access-Control headers bekommt der OPTIONS-Request angehängt
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
// Weitere Methoden wie PUT, DELETE ... möglich
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");
exit(0);
}
}
addCors();
Die GET- und POST-Anfragen sollten zu DIESEM Server funktionieren. Die OPTION-Anfrage sagt Deinem Browser, dass der Server CORS-fähig ist und die GET-Anfrage ihr Glück versuchen kann.
header(*) setzt die CORS_ Header und der Befehl$_SERVER['REQUEST_METHOD'] == 'OPTIONS'
sagt dem OPTION Request, welche HTTP-Methoden möglich sind.
Fall #2: Du brauchst schnell eine Lösung
Der folgende Abschnitt zeigt Dir schnellste Lösung. Später solltest Du Dir überlegen, ob Du nicht besser Fall 1 oder Fall 3 bearbeiten solltest.
In Firefox funktioniert das CORS-Everywhere Add-on am besten. https://addons.mozilla.org/de/firefox/addon/cors-everywhere/
Installiere Dir das Add-on, klicke auf das Symbol in der Toolbar, lade die Seite neu und BOOM – der CORS Fehler ist verschwunden.
In Chrome / Vivaldi (bzw. Chromium Engine Browser) funktioniert Vitvads CORS Plugin am besten.
Installiere Dir das Add-on, klicke auf das Symbol in der Toolbar und stelle den Schalter auf grün, lade die Seite neu und BOOM – der CORS Fehler ist verschwunden.
Sobald Du mit der Entwicklung Deiner App fertig bist, solltest Du Dir Gedanken über Fall 1 oder Fall 3 machen.
Fall #3: Du wirst den Backend-Deep NIE kennen.
Wenn Du eine langfristige bombenfeste Lösung brauchst, empfehle ich Dir einen PHP-Proxy zu schreiben. Dieser funktioniert folgendermaßen:
- Der Proxy nimmt jede URL per GET-Parameter auf. Der $_GET Befehl extrahiert die entsprechende URL heraus und speichert diese in einer Variable ab.
- Der Proxy ruft die Inhalte vom Server ab. Dafür verwendet PHP die Funktionen des cURL-Pakets. Dort kannst Du noch weitere Einstellungen für die GET-Abfrage vornehmen
- Der Proxy fügt die Allow-Origins-*-Header an.
- Der Proxy antwortet Deiner App. Dazu schreibt PHP die empfangenen Bytes mit dem Echo-Befehl in die PHP-Datei hinein.
Dieser Proxy arbeitet mit JSON: 'Content-type: application/json'
und G-Zipping zur Komprimierung header('Content-Encoding: gzip');
<?php
// CORS Header
header('Content-Encoding: gzip');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header("Access-Control-Allow-Headers: X-Requested-With");
header('Content-Type: application/json');
// Komprimierung starten
ob_start('ob_gzhandler');
$url = $_GET['url'];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-type: application/json'
));
$response = curl_exec($ch);
echo $response;
curl_close($ch);
ob_end_flush();
?>
Praxis in Ionic
Erstelle eine Ionic Projekt mit
ionic start ioniccors tabs --type=angular
Für einen Provider für den HTTP-Traffic hinzu.
ionic g service corstester
Füge zwei Buttons und ein Textfeld in src > app >tab1> tab1.pages.html ein
<ion-header>
<ion-toolbar>
<ion-title>
CORS Test
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-input placeholder="POST Input" [(ngModel)]="postContent"></ion-input>
<ion-button color="primary" (click)="sendGET()">Send a GET</ion-button>
<ion-button color="secondary" (click)="sendPOST()">Send a POST</ion-button>
</ion-content>
TypeScript:
import { Component } from '@angular/core';
import { CorstesterService } from '../corstester.service';
@Component({
selector: 'app-tab1',
templateUrl: 'tab1.page.html',
styleUrls: ['tab1.page.scss']
})
export class Tab1Page {
postContent;
constructor(public corstester:CorstesterService) {}
sendGET(){
this.corstester.get("https://fonts.googleapis.com/css?family=Open+Sans&display=swap").subscribe(font =>{
console.log(font);
});
}
sendPOST(){
this.corstester.post("https://lippke.li", {"hello":this.postContent}).subscribe(resp =>{
console.log(resp);
});
}
}
Provider:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class CorstesterService {
constructor(private http: HttpClient) { }
get(url){
return this.http.get(url, {responseType: 'text'});
}
post(url, content){
return this.http.post(url, content, {responseType: 'text'});
}
}
Erweiterungen: CORS vernichten
Wenn Du CORS verstanden hast, kannst Du auch mit anderen HTTP-Methoden experimentieren.
Teste doch einmal:
- DELETE – Kann Daten vom Server löschen
- PUT – Kann Dateien auf dem Server verändern
- TRACE – Zum Zurückverfolgen des HTTP-Befehls
Erstelle einen Datenbank-Eintrag mit einem POST-Befehl.
<?php
//CORS HEADER + Zipping
header('Content-Encoding: gzip');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header("Access-Control-Allow-Headers: X-Requested-With");
$connect = mysqli_connect("XXX.XXX.XXX.XXX:3306", "XXXXXX", "XXXXX", "XXXXX") or die("cannot connect");
$jsonTransaction = json_decode(file_get_contents('php://input'), true);
if ($connect->query($sql) === TRUE) { } else {
$conn->error;
}
if ($connect->query($sqlChangeAccount) === TRUE) { } else {
$conn->error;
}
$connect->close();
exit();
ob_end_flush();
?>
- Aktivere G-ZIP zur Komprimierung der Datenübertragung
- Setze die Standard-CORS-Header wie schon oben erwähnt
- Stelle eine Verbindung zur MySQL-Instanz her
- Mit file_get_contents erhältst Du den JSON-Inhalt
- Sende einen SQL-Befehl
- Schließe die Verbindung zur Datenbank
- Beende das G-Zipping
Fazit: Nie wieder CORS.
Ich hoffe, dass ich Euch ein besseres Verständnis von CORS verschaffen konnte.
Wenn CORS immer noch geht, kommentiere den Post.
Viel Spaß beim Programmieren.
Credits zu den verwendeten BildernIcons made by Vectors Market from www.flaticon.com is licensed by CC 3.0 BY
Das ist ja alles schön und gut. Aber wenn Hacker schadhaften Code von ihren Servern auf den Client laden wollen, können diese ihren Server doch so konfigurieren, dass er die entsprechenden CORS-Header setzt.
Nein. Du kannst CORS nur von bestimmten Quellen erlauben. Erlaube CORS nur von den Domains, die Du kennst bzw. wo die Verbindung hingeht.
Access-Control-Allow-Origin: https://meinedomainundnurmeine.com
Access-Control-Allow-Credentials: true