
本文详解如何在基于 kami 路由框架的 go web 服务中,安全、可靠地集成 rs/cors 中间件以解决带认证(basic auth + credentials)的跨域请求问题,涵盖 origin 白名单校验、凭证支持、options 预检处理及常见陷阱规避。
本文详解如何在基于 kami 路由框架的 go web 服务中,安全、可靠地集成 rs/cors 中间件以解决带认证(basic auth + credentials)的跨域请求问题,涵盖 origin 白名单校验、凭证支持、options 预检处理及常见陷阱规避。
Kami 是一个轻量、上下文感知的 Go HTTP 路由框架,但其本身不内置 CORS 支持,也不自动处理 OPTIONS 预检请求。当你的前端(如 https://www.php.cn/link/8e5687e2d6ab87e5da2f833f3e8986a4)通过 fetch(..., { credentials: 'include' }) 访问启用了 Basic Auth 的 API(如 http://localhost:8000/api/v1/systems)时,浏览器会强制发起预检(OPTIONS),并要求响应中同时满足:
-
Access-Control-Allow-Origin为明确的源(不可为*); -
Access-Control-Allow-Credentials: "true"; -
Vary: Origin头(用于缓存协商); -
Access-Control-Allow-Methods和Access-Control-Allow-Headers包含实际请求所用方法与头。
若任一条件缺失或冲突(例如 AllowCredentials: true 与 AllowedOrigins: ["*"] 共存),浏览器将静默拒绝响应——你甚至看不到完整错误日志,仅见 “No 'Access-Control-Allow-Origin' header is present”。
✅ 正确集成 rs/cors 与 Kami
rs/cors 是目前最推荐的 Go CORS 解决方案之一,它专为 net/http.Handler 设计,天然兼容 Kami(因其路由核心本质是 http.Handler)。关键在于:将 cors.Handler 作为中间件注册到 Kami 的路由前缀路径下,而非包裹整个 kami.Serve()。
以下是可直接运行的修复代码(基于你原始结构):
import (
"context"
"log"
"net/http"
"os"
"github.com/guregu/kami"
"github.com/rs/cors"
// ... 其他导入(mgo、json 等)
)
func main() {
session, err := mgo.Dial(mongoURI())
if err != nil {
log.Fatal(err)
}
defer session.Close()
systemsC := session.DB("").C("systems")
flag.Parse()
ctx := context.Background()
kami.Context = ctx
// ✅ Step 1: 创建 CORS 中间件(注意 AllowCredentials 和 AllowedOrigins 的严格匹配)
c := cors.New(cors.Options{
AllowedOrigins: []string{"https://www.php.cn/link/8e5687e2d6ab87e5da2f833f3e8986a4"}, // 开发环境;生产环境请替换为 https://your-app.com
AllowCredentials: true, // 必须开启,因前端使用 credentials: 'include'
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Content-Type", "Authorization", "X-Requested-With"},
// rs/cors 自动添加 Vary: Origin、处理 OPTIONS、设置 Access-Control-Max-Age 等,无需手动干预
})
// ✅ Step 2: 将 CORS 中间件注册到 /api/ 前缀(与 auth 中间件同级,顺序重要!)
kami.Use("/api/", c.Handler) // ← 关键:放在 auth 之前或之后均可,但必须注册在 /api/ 下
kami.Use("/api/", httpauth.SimpleBasicAuth(os.Getenv("BASIC_USERNAME"), os.Getenv("BASIC_PASSWORD")))
// ✅ Step 3: 定义业务路由(无需再手动写 CORS 头!)
kami.Get("/api/v1/systems", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
systems := []alpha.System{}
systemsMap := map[string][]alpha.System{}
err := systemsC.Find(nil).All(&systems)
if err != nil {
log.Println(err.Error())
http.Error(w, err.Error(), http.StatusNotFound)
return
}
systemsMap["systems"] = systems
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(&systemsMap); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
log.Println("Server starting on :8000")
kami.Serve() // 注意:kami.Serve() 内部已调用 http.ListenAndServe,无需额外包装
}
⚠️ 关键注意事项与避坑指南
-
Origin 与 Credentials 严格绑定:
AllowCredentials: true时,AllowedOrigins*绝不可含 `""**,必须为具体协议+域名+端口(如https://www.php.cn/link/8e5687e2d6ab87e5da2f833f3e8986a4或https://www.php.cn/link/73880287595cd991b800d5c69e16f33e。 -
中间件注册位置决定作用域:
kami.Use("/api/", c.Handler)表示该 CORS 策略仅应用于所有/api/开头的请求,精准且高效;避免全局注册导致非 API 路径被意外暴露。 -
无需手动处理 OPTIONS:
rs/cors默认拦截并响应所有 OPTIONS 请求,返回204 No Content,且自动注入全部必需 CORS 头(包括Access-Control-Allow-Methods,Access-Control-Allow-Headers,Vary: Origin),你完全不用在 handler 中if r.Method == "OPTIONS"。 -
Header 设置时机无风险:
rs/cors在ServeHTTP入口即写入响应头,确保早于任何业务逻辑,彻底规避w.Header().Set()调用过晚导致失效的问题。 -
生产环境动态白名单(进阶):若需支持多租户或灰度发布,可改用
AllowedOriginsFunc回调:AllowedOriginsFunc: func(origin string) bool { // 从数据库/配置中心查白名单,或做子域名校验(如 strings.HasSuffix(origin, ".example.com")) return origin == "https://admin.example.com" || origin == "https://web.example.com" },
? 总结
在 Kami 中启用 CORS,唯一正确且健壮的方式是使用 rs/cors 并将其作为前缀中间件注册(kami.Use("/api/", c.Handler))。它自动解决预检拦截、头注入时机、凭证兼容性等所有核心痛点。切勿尝试在 handler 内手动 w.Header().Set() —— 易遗漏、难维护、且在 AllowCredentials 场景下极易因 * 与 true 冲突而彻底失效。完成上述配置后,你的 React 应用即可通过 fetch('http://localhost:8000/api/v1/systems', { credentials: 'include' }) 成功通信。










