alloworigins设为"*"却开启allowcredentials是硬性错误,此时必须指定明确域名如"http://localhost:3000";options预检请求需被路由捕获,中间件须在所有路由注册前调用,且响应必须为204。

AllowOrigins设为"*"却开了AllowCredentials
这是最常踩的硬性错误。浏览器规范强制要求:只要响应头里有Access-Control-Allow-Credentials: true(即前端 fetch 用了 { credentials: 'include' }),Access-Control-Allow-Origin 就不能是 "*",必须是明确的协议+域名+端口,比如 "https://admin.example.com" 或 "http://localhost:3000"。
现象是:GET 请求看似正常,POST/PUT 带 Cookie 的请求直接失败,控制台只报模糊的 “CORS error”,Network 面板里看不到完整响应头——因为浏览器在解析阶段就静默丢弃了整个响应。
- 用
gin-contrib/cors时,config.AllowCredentials = true的前提下,config.AllowOrigins数组里绝不能出现"*" - 开发期别图省事写成
[]string{"*"},哪怕只配一个本地地址也要写全:[]string{"http://localhost:3000"} - 需要支持多个域名?老老实实列全:
[]string{"https://a.com", "https://b.net", "http://localhost:8080"},别指望"https://*.example.com"能自动匹配
OPTIONS 预检请求 404 或没返回 204
Gin 默认不注册 OPTIONS 方法路由,而预检请求就是 OPTIONS。如果你只写了 r.POST("/api/user", handler),那 OPTIONS /api/user 根本不会进中间件,直接 404 —— 前端甚至收不到预检响应,更别说后续请求了。
现象是:带自定义 header(如 Authorization)或非简单 Content-Type(如 application/json)的请求卡在预检,Network 面板显示 OPTIONS 返回 404 或 500。
- 正确做法是确保
OPTIONS请求能被路由捕获:要么显式注册r.OPTIONS("/api/user", handler),要么把 CORS 中间件放在r1.NoRoute(...)里兜底 - 中间件里判断
c.Request.Method == "OPTIONS"后,必须立刻调用c.Abort()并返回204 No Content,不能继续执行c.Next() - 用
gin-contrib/cors时,它内部已处理OPTIONS,但前提是中间件注册顺序正确:必须在r := gin.Default()之后、任何r.GET/POST之前调用r.Use(cors.New(...))
响应头设置太晚或重复覆盖
Go 的 http.ResponseWriter 头部只能在首次 WriteHeader() 或 Write() 之前设置。一旦 body 开始写入,再调 c.Header().Set() 就完全无效——你看到的响应头可能缺关键项,比如漏了 Access-Control-Allow-Methods。
现象是:部分跨域请求成功,部分失败;OPTIONS 响应里没有 Access-Control-Allow-Methods 或 Access-Control-Allow-Headers,导致预检被拒。
- 手写中间件时,所有
c.Header().Set()必须放在c.Next()之前,且不能依赖 handler 内部再补 - 如果用了
gin-contrib/cors,它会在中间件入口统一注入全部头,规避时机问题;但若混用多个中间件(比如自己又写了c.Header().Set("X-Trace-ID", ...)),要确认没覆盖掉 CORS 头 - 暴露自定义响应头(如
X-Total-Count)必须显式加Access-Control-Expose-Headers,否则前端response.headers.get("X-Total-Count")拿不到
分组路由(Group)下 CORS 不生效
用 r := r1.Group("/v1") 注册路由时,r.POST("/user", handler) 只会把 POST 方法注册到 engine,OPTIONS /v1/user 不会被该 handler 捕获——Gin 的分组路由不自动继承 OPTIONS 方法。
现象是:根路径(如 /)跨域正常,但所有分组下的接口(如 /v1/user)预检失败。
- 方案一:对每个分组接口,手动补一条
r.OPTIONS("/user", handler)(handler 可复用空函数) - 方案二:在顶层
r1.NoRoute(...)里注册一个全局 CORS 中间件,专门兜底所有未匹配的OPTIONS请求 - 别把 CORS 中间件只挂到分组上(
r.Use(corsMiddleware)),这无法覆盖OPTIONS路由缺失的问题
Origin 校验的动态性——硬编码域名列表在灰度、多租户或临时调试域名(如 ngrok)场景下很快失效,真要可靠,得用 AllowOriginFunc 做运行时白名单校验,且必须检查 scheme 是否合法、末尾斜杠是否一致。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











