OPTIONS请求被“非法拦截”而非404,根本原因是中间件顺序错误或逻辑缺陷导致静默失败:CORS中间件若置于鉴权中间件之后,OPTIONS会被JWT等提前返回401;若在c.Next()前调用c.JSON()等写响应方法,则预检无法返回204且响应头缺失,浏览器因头不全或状态码不符直接报错。

为什么 OPTIONS 请求会被“非法拦截”而不是 404?
这不是 Gin 主动拦截,而是中间件顺序或逻辑错误导致的静默失败。典型现象是:前端发 OPTIONS 请求后 Network 面板显示 pending 或直接超时,后端日志里完全没打印;或者返回 200 但缺关键响应头,浏览器仍报 Response to preflight request doesn't pass access control check。
根本原因有两个:c.Next() 前就写了响应(比如提前 c.JSON() 或 c.Status()),或 CORS 中间件被放在了鉴权类中间件(如 JWT 校验)之后——此时 OPTIONS 请求先被 JWT 拦下并返回 401,根本没机会走到 CORS 处理逻辑。
- 必须确保
r.Use(corsMiddleware)在所有路由注册前、且在任何鉴权/日志/恢复中间件之后(但必须在它们之前) - 中间件内部不能在
c.Next()前调用任何写响应的方法(c.AbortWithStatus()除外,仅用于OPTIONS) - 调试时用
curl -X OPTIONS -H "Origin: https://a.com" -I http://localhost:8080/api/foo直接看响应头,绕过前端干扰
gin-contrib/cors 的 AllowOrigins 匹配不是正则,别写 *.example.com
gin-contrib/cors 的 AllowOrigins 是精确匹配或前缀匹配,不支持通配符子域名语法。填 []string{"https://*.example.com"} 完全无效,请求 Origin 是 https://admin.example.com 也会被拒。
生产环境应显式列出完整域名,例如:[]string{"https://app.example.com", "https://admin.example.com", "https://staging.example.com"}。若需动态白名单(比如从 DB 查),不要用 gin-contrib/cors,改用手写中间件,在 origin := c.Request.Header.Get("Origin") 后做字符串比对或 host 解析。
- 允许凭证(
AllowCredentials: true)时,AllowOrigins绝不能含"*",否则浏览器直接丢弃整个响应(Network 面板都看不到) - 开发阶段可用
cors.Default()快速验证,但它等价于AllowOrigins: []string{"*"},上线前必须替换 - 如果前端用 IP + 端口访问(如
http://192.168.1.100:3000),Origin 字符串就是它,必须完整写进列表
手写中间件时,OPTIONS 处理必须包含三要素:头、状态码、终止
只写 c.Header("Access-Control-Allow-Origin", origin) 不够,也不该只返回 200。浏览器预检要求三个硬性条件同时满足:存在 Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers,且响应体为空、状态码为 204。
常见错误写法:if r.Method == "OPTIONS" { w.WriteHeader(200); return }(net/http)或 if c.Request.Method == "OPTIONS" { c.Status(200); return }(Gin)——漏设头、错用状态码、未清空响应体,都会导致预检失败。
- 正确做法:设置全部必要头 → 调用
c.AbortWithStatus(http.StatusNoContent)(Gin)或w.WriteHeader(http.StatusNoContent)(net/http)→ 立即 return - 头必须包括:
Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers;带凭证时还需Access-Control-Allow-Credentials: "true" - 别在
OPTIONS分支里调用c.Next(),那会导致后续中间件重复执行,可能触发 JWT 校验失败等连锁问题
带 body 的 OPTIONS 请求会绕过 gin-contrib/cors 默认逻辑
某些 iOS WebView 或旧版 Electron 客户端会发送带 body 的 OPTIONS 请求(规范允许,但非常规)。而 gin-contrib/cors 内部默认只处理无 body 的预检,遇到带 body 的请求会跳过,继续往下走——结果被后续中间件(如 JWT)当成非法请求拦截,返回 401 或 400。
这不是 bug,是设计取舍。解决方案只有两个:要么禁用这类客户端的 body(前端控制),要么换手写中间件并显式检查 c.Request.Body。
- 手写时加判断:
if c.Request.Method == "OPTIONS" && c.Request.ContentLength > 0,然后同样设头 +c.AbortWithStatus(204) - gin-contrib/cors 无法通过配置解决此问题,它的
AllowMethods只控制响应头,不干预请求体校验 - 调试时注意抓包看 OPTIONS 请求是否带
Content-Length或Content-Type,这是关键线索


















