Cross-Origin-Requests (CORS) im Griff

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 mit Access-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.
  • 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;
    }
}
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:

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.