CORS (Cross-Origin Resource Sharing) kann einem den letzten Nerv rauben, weil es zwar einfach scheint und eigentlich auch ist, es aber einige Details zu beachten gibt, damit alles klappt.
Meine Erkenntnisse dazu:
- Grundprinzip: Es geht um „Cross-Origin-Requests“, welche normalerweise durch die Same-Origin-Policy (SOP) verhindert werden:
- JavaScript wird von Webserver „quellsrv“ geholt. JS ruft Server „zielsrv“ auf.
- In den Aufruf-Header wird vom JS
Origin: http://quellsrv/
geschrieben. - zielsrv muss in der Antwort die korrekten CORS-Header enthalten.
- Fehlen die Header oder passen nicht, kommt die Antwort zwar im Browser an, wird aber nicht verarbeitet und nicht an den aufrufenden JS-Code übergeben. Dieser erhält stattdessen einen Fehler.
Access-Control-Allow-Origin: *
kombiniert mitAccess-Control-Allow-Credentials: true
führt ebenfalls zum Ablehnen der Antwort-Verarbeitung im Browser.- D.h. es muss der Quellserver im
Access-Control-Allow-Origin
explizit drin stehen – als einziger Eintrag.
- D.h. es muss der Quellserver im
- Je nach Implementierung werden die CORS-Header überhaupt nur geschickt, wenn der Origin-Header im Request enthalten war. D.h. mit einem „normalen“ Request (z.B. manuell im Browser) kann man nicht prüfen, ob die Header korrekt gesendet werden. Besser stattdessen z.B.:
curl -v --header "Origin: http://quellsrv/" http://zielsrv:8080/context/endpoint
oder http://test-cors.org
-
Die folgende Java-Implementierung mit Hilfe von
org.jboss.resteasy
schickt tatsächlich 1. die CORS-Header nur, wenn es einen Origin-Header gab und 2. wird der*
im Header ersetzt durch den tatsächlichen Wert im Origin-Header, sodass man die o.g. Problematik (* kombiniert mit AllowCredentials) nicht hat.
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;
import org.jboss.resteasy.plugins.interceptors.CorsFilter;
@Provider
public class CorsFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
CorsFilter corsFilter = new CorsFilter();
corsFilter.getAllowedOrigins().add("*");
corsFilter.setAllowCredentials(true);
corsFilter.setCorsMaxAge(1209600);
context.register(corsFilter);
return true;
}
}
- In einem node.js/Express-Webserver lässt sich das *-Problem so umgehen:
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.headers["origin"]);
res.header("Access-Control-Allow-Credentials", "true");
next();
});
app.use(handleRequest);
api.serve();
-
Bei verändernden Aufrufen (PUT, POST mit Daten im Body oder
X-...
-Headern (Details siehe hier), wird ein sogenannter Preflight-Request durchgeführt: Vor dem eigentlichen Request wird ein HTTP-OPTIONS-Request ausgeführt, um die CORS-Header zu prüfen. -
Die Antwort auf OPTIONS-Calls wird im Browser gecacht. Und zwar gesteuert durch den Server-Antwort-Header
Access-Control-Max-Age: 9999
(in Sekunden). Firefox begrenzt das jedoch auf 24h – siehe hier.
Weitere Links:
- Gute und (relative) kurze Erläuterung der Details auf mozilla.org.
- node.js/Express-Framwork Hello-World-Beispiel.