beego默认不开启跨域支持,cors.allow插件常失效的根本原因是未正确处理预检(options)请求:插件仅注入响应头但不注册options路由,导致带凭证或自定义头的请求在预检阶段被404/405拦截;且allowcredentials为true时access-control-allow-origin不可设为"*",必须显式指定域名并确保预检与主请求响应头完全一致。

Beego 默认不开启跨域支持,直接加 cors.Allow 过滤器经常失败——根本原因不是配置漏了,而是预检(OPTIONS)请求没被正确路由或响应头冲突,尤其当启用 AllowCredentials: true 时,Access-Control-Allow-Origin 不能为 "*"。
为什么 cors.Allow 插件常失效
Beego 的 github.com/astaxie/beego/plugins/cors 插件在 v1.x 中对预检请求的处理较粗粒度:它只注入响应头,但不主动注册 OPTIONS 路由;而浏览器遇到带凭证(如 cookie、Authorization)或自定义 header(如 X-Token)的请求时,一定会先发 OPTIONS。如果该请求没匹配到任何 handler,Beego 会返回 404 或 405,导致预检失败。
常见错误现象包括:
Request header field X-Token is not allowed by Access-Control-Allow-HeadersResponse to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'- 前端控制台显示
Failed to load http://localhost:8080/v1/user: No 'Access-Control-Allow-Origin' header is present,但后端日志里根本没收到该请求——说明卡在预检阶段
必须手动注册 OPTIONS 路由并确保响应头一致
预检请求需要真实 endpoint 响应,且响应头必须和后续实际请求的头完全匹配(尤其是 Access-Control-Allow-Headers 和 Access-Control-Allow-Methods)。不能只靠中间件“塞头”,还要让路由能接住 OPTIONS。
在 router.go 的 init() 函数中:
func init() {
// 1. 全局插入 CORS 头(用于非预检请求)
beego.InsertFilter("*", beego.BeforeRouter, func(ctx *context.Context) {
ctx.Output.Header("Access-Control-Allow-Origin", "http://localhost:8080")
ctx.Output.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
ctx.Output.Header("Access-Control-Allow-Headers", "Origin,Content-Type,Authorization,X-Token,X-Requested-With")
ctx.Output.Header("Access-Control-Allow-Credentials", "true")
ctx.Output.Header("Access-Control-Expose-Headers", "Content-Length")
ctx.Output.Header("Access-Control-Max-Age", "1728000")
})
// 2. 显式注册 OPTIONS 路由(关键!)
ns := beego.NewNamespace("/v1",
beego.NSCond(func(ctx *context.Context) bool {
return ctx.Input.Method == "OPTIONS"
}),
beego.NSRouter("/*", &controllers.BaseController{}, "options:Options"),
)
beego.AddNamespace(ns)
// 3. 正常业务路由(保持原有逻辑)
beego.Router("/v1/user", &controllers.UserController{})
}
注意:NSCond 是 Beego 1.12+ 推荐方式,比用 * 模糊匹配更安全;若用旧版,可用 beego.NSNamespace("/*", ...) 配合 NSRouter("/*", ..., "options:Options")。
BaseController.Options() 方法要精简且可复用
这个方法唯一作用是让预检通过,内容无关紧要,但必须调用 c.ServeJSON() 或至少写入响应体(否则某些浏览器可能拒绝后续请求)。
在 controllers/base.go 中:
func (c *BaseController) Options() {
// 不需要业务逻辑,但必须设置相同响应头
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Origin", "http://localhost:8080")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Headers", "Origin,Content-Type,Authorization,X-Token,X-Requested-With")
c.Ctx.ResponseWriter.Header().Set("Access-Control-Allow-Credentials", "true")
c.Data["json"] = map[string]interface{}{"status": "ok"}
c.ServeJSON()
}
关键点:
- 不要用
c.Output.JSON(),它可能覆盖已设 header;直接操作c.Ctx.ResponseWriter.Header() - 如果多个 controller 都需要,把 header 设置逻辑抽成公共函数,避免各处硬编码不一致
- 若前端用
withCredentials: true,Access-Control-Allow-Origin必须写具体域名,不能用"*"
调试时最容易忽略的三个细节
跨域问题往往卡在看似无关的配置上:
-
AllowCredentials: true时,Access-Control-Allow-Origin和Access-Control-Allow-Headers必须显式列出所有前端实际发送的字段,比如前端发了X-Token,但AllowHeaders里没写,预检就失败 - Beego 的
InsertFilter是按注册顺序执行的,如果其他 filter(如鉴权 filter)提前写了响应或跳转,CORS 头可能被丢弃——确保 CORS filter 在最前 - 开发环境用
http://localhost:8080,生产环境可能是https://app.example.com,别把测试用的 origin 写死在代码里,建议从环境变量读取
真正难的不是加几行配置,而是让预检请求和主请求的响应头完全对齐,且路由层不拦截 OPTIONS。漏掉任一环,浏览器都会静默失败。











