Erstellung einer benutzerdefinierten CORS-Middleware in Go-Webservern
Daniel Hayes
Full-Stack Engineer · Leapcell

Einleitung
In modernen Webanwendungen ist es zunehmend üblich, dass Webanwendungen mit Ressourcen interagieren, die auf verschiedenen Domänen gehostet werden. Denken Sie an eine Frontend-Anwendung, die von app.example.com bedient wird und API-Aufrufe an einen Backend-Dienst unter api.example.com sendet, oder an die Integration mit Diensten von Drittanbietern. Dieses gängige Szenario bringt sofort einen grundlegenden Sicherheitsmechanismus hervor, der von Webbrowsern durchgesetzt wird: die Same-Origin Policy. Diese Richtlinie schränkt, obwohl sie für die Sicherheit von entscheidender Bedeutung ist, legitime Cross-Origin-Interaktionen ein, was zu frustrierenden "von CORS blockiert"-Fehlern führt. Um dies zu überwinden, wurde der Cross-Origin Resource Sharing (CORS)-Mechanismus eingeführt, der es Servern ermöglicht, ausdrücklich die Erlaubnis für Cross-Origin-Anfragen zu erteilen. Viele Go-Web-Frameworks bieten zwar integrierte CORS-Lösungen, aber das Verständnis und die manuelle Implementierung einer CORS-Middleware vermitteln unschätzbare Einblicke in deren Funktionsweise, bieten überlegene Flexibilität und sind unerlässlich, um Sicherheitsrichtlinien genau auf die Bedürfnisse Ihrer Anwendung abzustimmen. Dieser Artikel führt Sie durch den Prozess der manuellen Implementierung und Konfiguration einer CORS-Middleware in einem Go-Webserver.
Verständnis und Implementierung einer benutzerdefinierten CORS-Middleware
Bevor wir uns dem Code widmen, definieren wir einige Kernkonzepte im Zusammenhang mit CORS, die für unsere Diskussion von entscheidender Bedeutung sind.
Kernterminologie
- Same-Origin Policy (SOP): Ein grundlegendes Sicherheitskonzept, das einschränkt, wie ein Dokument oder Skript, das von einem Ursprung geladen wurde, mit einer Ressource von einem anderen Ursprung interagieren kann. Ein Ursprung wird durch das Protokoll, den Host und den Port der URL definiert.
- Cross-Origin-Request: Jede Anfrage an eine Ressource, deren Ursprung vom Ursprung des anfragenden Dokuments abweicht.
- CORS (Cross-Origin Resource Sharing): Eine Reihe von HTTP-Headern, die Webbrowser und Server verwenden, um zu bestimmen, ob eine Webseite Cross-Origin-Anfragen stellen darf.
- Preflight Request: Für bestimmte "komplexe" Anfragen (z. B. HTTP-Methoden außer
GET,HEAD,POSTmit einfachen Inhaltstypen oder Anfragen mit benutzerdefinierten Headern) senden Browser eine automatische "Preflight"OPTIONS-Anfrage an den Server, bevor die eigentliche Anfrage gesendet wird. Dieser Preflight prüft, welche CORS-Richtlinien vom Server für die nachfolgende tatsächliche Anfrage zulässig sind. - CORS-Header:
Access-Control-Allow-Origin: Gibt an, welche Ursprünge auf die Ressource zugreifen dürfen. Kann ein bestimmter Ursprung oder*für jeden Ursprung sein.Access-Control-Allow-Methods: Gibt die für Cross-Origin-Anfragen zulässigen HTTP-Methoden an (z. B.GET,POST,PUT,DELETE).Access-Control-Allow-Headers: Listet die HTTP-Header auf, die in der tatsächlichen Anfrage verwendet werden können.Access-Control-Allow-Credentials: Gibt an, ob der Browser Anmeldeinformationen (Cookies, HTTP-Authentifizierung) mit der Cross-Origin-Anfrage senden soll. Muss auftruegesetzt werden, wenn unterstützt.Access-Control-Expose-Headers: Ermöglicht es dem Server, Header zu whitelisten, die Browser im Frontend-JavaScript-Code verfügbar machen dürfen.Access-Control-Max-Age: Gibt an, wie lange die Ergebnisse einer Preflight-Anfrage zwischengespeichert werden können.
Das Prinzip der CORS-Middleware
Eine CORS-Middleware sitzt konzeptionell zwischen der eingehenden HTTP-Anfrage und den Handlern Ihrer Anwendung. Ihre Hauptverantwortung besteht darin, die Anfrage zu prüfen, insbesondere den Origin-Header, und basierend auf vordefinierten Regeln die entsprechenden CORS-Header zur Antwort hinzuzufügen. Sie muss auch explizit OPTIONS-Preflight-Anfragen behandeln.
Implementierung der benutzerdefinierten CORS-Middleware
Lassen Sie uns eine flexible CORS-Middleware in Go erstellen. Wir definieren eine Konfigurationsstruktur, um unsere zulässigen Ursprünge, Methoden und Header zu verwalten.
package main import ( "log" "net/http" "strings" "time" ) // CORSConfig holds the configuration for our CORS middleware. type CORSConfig struct { AllowedOrigins []string AllowedMethods []string AllowedHeaders []string ExposedHeaders []string AllowCredentials bool MaxAge time.Duration // Duration for Access-Control-Max-Age header } // NewCORSConfig creates a default CORS configuration. func NewCORSConfig() *CORSConfig { return &CORSConfig{ AllowedOrigins: []string{"*"}, // Allow all origins by default (be cautious in production) AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Content-Type", "Content-Length", "Accept-Encoding", "X-CSRF-Token", "Authorization"}, ExposedHeaders: []string{} , AllowCredentials: false, MaxAge: 10 * time.Minute, } } // CORSMiddleware is an http.Handler that wraps another http.Handler to provide CORS functionality. func CORSMiddleware(config *CORSConfig, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") if origin == "" { // Not a CORS request, pass through next.ServeHTTP(w, r) return } // Check if the origin is allowed isOriginAllowed := false if len(config.AllowedOrigins) == 1 && config.AllowedOrigins[0] == "*" { isOriginAllowed = true // All origins allowed } else { for _, allowed := range config.AllowedOrigins { if allowed == origin { isOriginAllowed = true break } } } if !isOriginAllowed { // If origin not allowed, don't add CORS headers and deny the request log.Printf("CORS: Origin '%s' not allowed.", origin) w.WriteHeader(http.StatusForbidden) return } // Add Access-Control-Allow-Origin header if len(config.AllowedOrigins) == 1 && config.AllowedOrigins[0] == "*" { w.Header().Set("Access-Control-Allow-Origin", "*") } else { w.Header().Set("Access-Control-Allow-Origin", origin) } // If credentials are allowed, set the header if config.AllowCredentials { w.Header().Set("Access-Control-Allow-Credentials", "true") } // Handle preflight OPTIONS requests if r.Method == http.MethodOptions { // Add Access-Control-Allow-Methods header w.Header().Set("Access-Control-Allow-Methods", strings.Join(config.AllowedMethods, ", ")) // Add Access-Control-Allow-Headers header based on requested headers requestedHeaders := r.Header.Get("Access-Control-Request-Headers") if requestedHeaders != "" { allowedRequestHeaders := make([]string, 0) for _, reqHeader := range strings.Split(requestedHeaders, ",") { reqHeader = strings.TrimSpace(reqHeader) for _, allowedConfigHeader := range config.AllowedHeaders { if strings.EqualFold(reqHeader, allowedConfigHeader) { allowedRequestHeaders = append(allowedRequestHeaders, reqHeader) break } } } if len(allowedRequestHeaders) > 0 { w.Header().Set("Access-Control-Allow-Headers", strings.Join(allowedRequestHeaders, ", ")) } else { // If no requested headers are explicitly allowed, respond with no headers (browser will deny) log.Printf("CORS: No requested headers allowed for origin '%s'. Requested: %s", origin, requestedHeaders) w.WriteHeader(http.StatusForbidden) return } } else { // If no specific headers are requested, just send configured allowed headers w.Header().Set("Access-Control-Allow-Headers", strings.Join(config.AllowedHeaders, ", ")) } // Set Access-Control-Max-Age if config.MaxAge > 0 { w.Header().Set("Access-Control-Max-Age", string(config.MaxAge/time.Second)) } // Respond to preflight with 204 No Content w.WriteHeader(http.StatusNoContent) return } // For actual requests, set exposed headers if configured if len(config.ExposedHeaders) > 0 { w.Header().Set("Access-Control-Expose-Headers", strings.Join(config.ExposedHeaders, ", ")) } // Pass the request to the next handler next.ServeHTTP(w, r) }) }
Erklärung der CORS-Middleware-Logik:
- Prüfung des Anfrageursprungs: Zuerst wird der
Origin-Header geprüft. Wenn er fehlt, handelt es sich nicht um eine Cross-Origin-Anfrage, und die Verarbeitung wird ohne CORS-Header an den nächsten Handler weitergeleitet. - Überprüfung des zulässigen Ursprungs: Der
Origin-Header wird gegenconfig.AllowedOriginsverglichen. Wenn*angegeben ist, sind alle Ursprünge zulässig. Andernfalls wird geprüft, ob der spezifische Ursprung in der zulässigen Liste enthalten ist. Wenn nicht, wird die Anfrage mithttp.StatusForbiddenabgelehnt. Access-Control-Allow-Origin: Dieser Header wird entweder mit dem spezifischen zulässigenOriginaus der Anfrage oder mit*gesetzt, abhängig von der Konfiguration.Access-Control-Allow-Credentials: Wennconfig.AllowCredentialstrueist, wird dieser Header hinzugefügt. Beachten Sie, dassAccess-Control-Allow-Origin: *nicht zusammen mitAccess-Control-Allow-Credentials: trueverwendet werden kann. Unsere aktuelle Logik fürAccess-Control-Allow-Originbehandelt dies, indem sie den spezifischenoriginsetzt, wenn Anmeldeinformationen zulässig sind.- Preflight
OPTIONS-Anfragen: Wenn die Anfrage-MethodeOPTIONSist:Access-Control-Allow-Methodswird basierend aufconfig.AllowedMethodsgesetzt.Access-Control-Request-Headerswird vom Client gelesen. Dann wird jeder angeforderte Header geprüft, ob er inconfig.AllowedHeadersenthalten ist, und der AntwortheaderAccess-Control-Allow-Headerswird nur mit den zulässigen angeforderten Headern erstellt. Wenn benutzerdefinierte Header angefordert, aber nicht zulässig sind, schlägt der Preflight fehl.Access-Control-Max-Agewird basierend aufconfig.MaxAgegesetzt.- Die Middleware antwortet mit
http.StatusNoContent(204) und beendet die Anfrage, da eine Preflight-Anfrage keinen Körper erwartet.
- Tatsächliche Anfragen: Für jede andere HTTP-Methode (tatsächliche Anfragen) wird
Access-Control-Expose-Headersgesetzt, wenn konfiguriert, wodurch Client-seitiges JavaScript auf bestimmte Antwortheader zugreifen kann. Dann wird die Anfrage an dennext-Handler zur tatsächlichen Geschäftslogik weitergeleitet.
Integration der Middleware in einen Go-Server
Hier ist ein Beispiel, wie diese CORSMiddleware mit tatsächlichen API-Handlern verwendet werden kann.
package main import ( "encoding/json" "fmt" "log" "net/http" "time" ) // MyHandler is a simple handler for demonstration. func MyHandler(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "Hello from Go API!"}) return } if r.Method == http.MethodPost { var data map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&data); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } log.Printf("Received POST data: %+v", data) w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "received", "data": fmt.Sprintf("%+v", data)}) return } http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } func main() { // Configure CORS corsConfig := NewCORSConfig() corsConfig.AllowedOrigins = []string{ "http://localhost:3000", // Example frontend origin "http://127.0.0.1:3000", } corsConfig.AllowedMethods = []string{"GET", "POST", "PUT", "DELETE"} corsConfig.AllowedHeaders = []string{"Content-Type", "Authorization"} // Allow custom Authorization header corsConfig.AllowCredentials = true // Allow cookies/auth headers corsConfig.MaxAge = 1 * time.Hour // Cache preflights for 1 hour // Create a new ServeMux mux := http.NewServeMux() // Apply the CORS middleware to your handler // The order matters: CORS middleware should wrap your actual handler. corsHandler := CORSMiddleware(corsConfig, http.HandlerFunc(MyHandler)) mux.Handle("/api/data", corsHandler) // Another handler without CORS middleware (for demonstration of selective application) mux.HandleFunc("/public", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "This is a public endpoint, no CORS applied here.") }) log.Println("Server starting on port 8080...") if err := http.ListenAndServe(":8080", mux); err != nil { log.Fatalf("Server failed: %v", err) } }
In der main-Funktion:
- Wir initialisieren eine
CORSConfigund passen sie an. Für die Produktion solltenAllowedOriginsspezifisch für Ihre Frontend-Domänen sein, nicht*.AllowCredentialssollte auftruegesetzt werden, wenn Ihr Frontend Cookies oder HTTP-Authentifizierungsheader in Cross-Origin-Anfragen senden muss. - Wir erstellen einen
mux(Router). - Wir umschließen unseren
MyHandlermit derCORSMiddlewareunter Verwendung unserer benutzerdefinierten Konfiguration. - Wir registrieren den
corsHandler, um Anfragen an/api/datazu bearbeiten. Das bedeutet, dass jede Anfrage an/api/datazuerst unsere CORS-Logik durchläuft.
Anwendungsfälle
- Single-Page Applications (SPAs): Ein gängiges Szenario, bei dem das Frontend (z. B. React, Angular, Vue) auf einer anderen Domäne läuft und Daten von einer separaten Backend-API abruft.
- Microservices: Wenn Dienste über verschiedene Domänen oder Ports innerhalb eines größeren verteilten Systems hinweg kommunizieren.
- Integrationen mit APIs von Drittanbietern: Wenn Ihr Frontend eine API auf einer anderen Domäne aufruft, muss der Serverseitige dieser API CORS implementieren. Umgekehrt, wenn Ihre API von Drittanbietern genutzt wird, benötigen Sie dies.
- Entwicklungsumgebungen: Oft laufen Entwicklungsserver auf anderen Ports als das Backend-Entwicklungssystem, was CORS erforderlich macht.
Fazit
Die manuelle Implementierung und Konfiguration einer CORS-Middleware in einem Go-Webserver bietet eine feingranulare Kontrolle über die Cross-Origin-Ressourcenfreigabe, einen kritischen Aspekt der Web-Sicherheit und -Funktionalität. Durch das Verständnis der Kernkonzepte von CORS – wie Preflight-Anfragen und spezifische HTTP-Header – und die sorgfältige Erstellung einer Middleware können Entwickler sicherstellen, dass ihre Anwendungen sicher und effektiv über verschiedene Ursprünge hinweg kommunizieren. Dieser Ansatz bietet Flexibilität und vertieft das Verständnis, sodass Sie komplexe Cross-Origin-Herausforderungen bewältigen können, die auf die individuellen Anforderungen Ihrer Anwendung zugeschnitten sind, und so robuste und sichere Webintersaktionen gewährleisten.

