在 Golang 的 net/http 包中,Set-Cookie 响应头用于设置 cookie。Domain 属性通常用来指定 cookie 有效的域名。对于一个 cookie,你无法直接在一个 Domain 字段中填写多个域名。
HTTP cookie 的 Domain 属性被设计为匹配单个域名或其子域名。当浏览器收到一个设置 cookie 的响应时,它会查看 Domain 属性,并决定是否将该 cookie 发送到服务器。
但是,有几种方法可以“模拟”或实现跨多个域名使用 cookie 的效果,这取决于你的具体需求:
这是最直接也是最常见的方法。你可以为每个你希望 cookie 生效的域名单独设置一个 cookie。go
package main
import (
"net/http"
"time"
)
func setCookiesHandler(w http.ResponseWriter, r *http.Request) {
// Cookie for example.com
cookie1 := &http.Cookie{
Name: "session_id",
Value: "user123",
Domain: "example.com", // Specify the domain
Path: "/",
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie1)
// Cookie for another-example.org
cookie2 := &http.Cookie{
Name: "session_id", // Can be the same name if they represent the same logical data
Value: "user123", // Or different values if needed
Domain: "another-example.org", // Specify the other domain
Path: "/",
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie2)
w.Write([]byte("Cookies set for multiple domains!"))
}
func main() {
http.HandleFunc("/set-cookies", setCookiesHandler)
http.ListenAndServe(":8080", nil)
}
原理:
* 浏览器收到 example.com 的响应时,它会存储 session_id cookie,并只在访问 example.com 或其子域名时发送。
* 浏览器收到 another-example.org 的响应时,它会存储另一个 session_id cookie,并只在访问 another-example.org 或其子域名时发送。
缺点:
* 管理成本增加,需要为每个域名维护一个 cookie。
* 如果域名非常多,可能会产生大量的 Set-Cookie 头部。
HTTP cookie 的 Domain 属性支持通配符,但只能在域名前使用一个星号 (*),并且该星号必须紧跟着一个点 (.)。例如:
* *.example.com 会匹配 www.example.com, app.example.com, mail.example.com 等,但不会匹配 example.com 本身。
* example.com 会匹配 example.com 和其子域名(如 www.example.com)。
这是最接近“单个 Domain 项包含多个域名”的方式,但它实际上是匹配一个域名及其所有子域名。go
package main
import (
"net/http"
"time"
)
func setWildcardCookieHandler(w http.ResponseWriter, r *http.Request) {
// This cookie will be sent to www.example.com, app.example.com, etc.
// but NOT to example.com itself.
cookie := &http.Cookie{
Name: "shared_token",
Value: "abcxyz789",
Domain: ".example.com", // Note the leading dot
Path: "/",
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
w.Write([]byte("Cookie set for .example.com (subdomains)!"))
}
func main() {
http.HandleFunc("/set-wildcard-cookie", setWildcardCookieHandler)
http.ListenAndServe(":8080", nil)
}
请注意:
* Domain: ".example.com" (带点) 和 Domain: "example.com" (不带点) 在行为上有所不同:
* Domain: "example.com":匹配 example.com 及其子域名(如 www.example.com)。
* Domain: ".example.com":匹配 example.com 的所有子域名(如 www.example.com, app.example.com),但通常不包括 example.com 本身(这取决于浏览器实现,但通常是这样)。
* 你无法使用 Domain: "*.example.com, another-example.org" 这样的语法。
如果你需要让不同顶级域名的应用共享 cookie 或进行通信,通常的最佳实践是使用一个 后端代理。
* 所有请求都先经过一个中心化的代理服务。
* 代理服务根据请求的原始域名,将其路由到相应的后端应用。
* 在这种模式下,cookie 只需要设置给 代理服务的域名。
* 当请求到达后端应用时,你可以通过请求头(如 X-Forwarded-Host 或 X-Original-Host)来识别原始请求的域名。
示例流程:
1. 用户访问 app1.mydomain.com。
2. 请求被发送到你的主应用(例如 api.mydomain.com)作为一个代理。
3. 主应用设置一个 session_id cookie,Domain: "mydomain.com"。
4. 主应用将请求转发给实际处理 app1.mydomain.com 的后端服务。
5. 当用户访问 app2.mydomain.com 时,请求也经过主应用(代理)。
6. 主应用检测到是 app2.mydomain.com 的请求,并转发给相应的后端服务。
7. 浏览器会将 session_id cookie (Domain: "mydomain.com") 发送给主应用,主应用可以从中读取用户信息,然后根据原始域名(app1.mydomain.com 或 app2.mydomain.com)进行相应的处理。
在 Golang 中实现代理:
你可以使用 net/http/httputil.ReverseProxy 来实现一个简单的代理。go
package main
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"time"
)
func main() {
// Configuration for backend services
services := map[string]string{
"app1.localhost": "http://localhost:8001", // Backend for app1.localhost
"app2.localhost": "http://localhost:8002", // Backend for app2.localhost
}
// Your main application domain
mainDomain := "localhost" // Or your actual domain for the proxy
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
host := r.Host // e.g., "app1.localhost:8080"
// Extract the subdomain part (or the full host if no subdomain)
targetURL, ok := services[host]
if !ok {
http.Error(w, "Service not found", http.StatusNotFound)
return
}
proxyURL, _ := url.Parse(targetURL)
proxy := httputil.NewSingleHostReverseProxy(proxyURL)
// Set a cookie on the main domain if not already set
_, err := r.Cookie("session_id")
if err != nil || err == http.ErrNoCookie {
cookie := &http.Cookie{
Name: "session_id",
Value: "shared_token_from_proxy",
Domain: mainDomain, // Set cookie on the proxy's domain
Path: "/",
Expires: time.Now().Add(24 * time.Hour),
HttpOnly: true,
Secure: true,
SameSite: http.SameSiteLaxMode,
}
http.SetCookie(w, cookie)
fmt.Println("Setting session_id cookie on", mainDomain)
} else {
fmt.Println("session_id cookie already exists:", err)
}
// You might want to set a header to indicate the original host to the backend
r.Header.Set("X-Forwarded-Host", host)
r.Header.Set("X-Original-Host", host)
proxy.ServeHTTP(w, r)
})
fmt.Println("Starting proxy server on :8080")
fmt.Println("Proxying to:", services)
fmt.Println("Cookies will be set on:", mainDomain)
http.ListenAndServe(":8080", nil)
}
重要提示:
* 在上面的代理示例中,cookie 被设置在 localhost (或你的 mainDomain) 上。这意味着浏览器会在访问 localhost:8080 (代理服务器) 时发送此 cookie。
* 实际的后端服务 (app1.localhost, app2.localhost) 需要被配置为可以通过代理访问,并且它们可以通过 X-Forwarded-Host 等头信息来识别用户是从哪个子域来的。
* 这个模式最适合于一个主域名下的多个子域名,或者通过同一个入口点服务的多个不同的应用。
* 无法直接在 Domain 字段填写多个域名。
* 最常见的方式是为每个目标域名单独设置一个 cookie。
* Domain: ".example.com" 形式可以为 example.com 的所有子域名设置 cookie。
* 使用后端代理是实现跨不同顶级域名(或一组应用)共享状态的更健壮和可扩展的解决方案。
选择哪种方法取决于你的具体部署环境和需求。如果你是在同一父域下的不同子域之间,Domain: ".yourdomain.com" 可能就足够了。如果是在完全不同的顶级域名之间,后端代理是更推荐的方式。