OPTIONS预检请求未被中间件捕获是“GET能通、POST报错”的主因;浏览器对非简单请求先发OPTIONS,若路由未注册或中间件未覆盖,直接404/405,无法执行CORS逻辑。

OPTIONS预检请求没被中间件捕获
这是最常导致“GET能通、POST报错”的原因。浏览器在发非简单请求(如带 Authorization 头、Content-Type: application/json 或用 PUT/DELETE)前,会先发一个 OPTIONS 请求。如果 Gin 路由没注册该路径,或中间件没覆盖到它,请求就直接 404 或 405,根本走不到 CORS 逻辑。
实操建议:
-
r.Use(corsMiddleware)必须放在所有r.GET、r.POST之前,且不能漏掉r.Any()或r.OPTIONS()路由的覆盖 - 自己写的中间件里,
if c.Request.Method == "OPTIONS"分支必须调用c.AbortWithStatus(204)并return,不能只写头不终止 - 用
gin-contrib/cors时,确认没在某个子路由上单独调用router.OPTIONS()—— 它会绕过中间件
Access-Control-Allow-Origin 和 credentials 冲突
只要后端返回了 Access-Control-Allow-Credentials: true,浏览器就坚决拒绝 Access-Control-Allow-Origin: "*"。哪怕你只在本地开发,前端用了 credentials: 'include',这个组合就会让整个响应被静默丢弃。
实操建议:
- 生产环境必须把
AllowOrigins设为完整协议+域名+端口列表,例如[]string{"https://app.example.com", "https://admin.example.com"} - 别用
AllowAllOrigins: true配合AllowCredentials: true——gin-contrib/cors会自动禁用该配置,但不会报错,只默默失效 - 动态匹配更安全:读取请求头
Origin,白名单校验后原样回写,而不是硬编码或通配
中间件注册顺序错误或提前写响应头
Gin 中间件链是线性执行的。如果 CORS 中间件在 c.Next() 之后才写响应头,那对 OPTIONS 请求就完全无效;如果在 c.Next() 之前就写了头又没 abort,后续 handler 可能 panic 或重复写头。
实操建议:
- 中间件必须在
r := gin.Default()后、任何r.GET前调用r.Use() - 自己写中间件时,
OPTIONS分支要放在最前面,c.AbortWithStatus(204)后立即return - 避免在中间件里调用
c.JSON()或c.String()—— 这些会提前写响应体,破坏 CORS 的短路逻辑
反向代理(Nginx/Traefik)覆盖或丢弃响应头
上线后 CORS 突然失效,八成是 Nginx 没透传 Origin 头,或自己加了冲突的 add_header 覆盖了后端返回的 CORS 头。Uvicorn、Traefik 等代理也有类似行为。
实操建议:
- Nginx 配置里加:
proxy_set_header Origin $http_origin;,否则后端读不到原始Origin - 不要用多个
add_header设置同一字段(如重复设Access-Control-Allow-Origin),Nginx 默认只保留最后一个 - 用
curl -H "Origin: https://example.com" -I http://localhost:8080/api直连后端验证,排除代理干扰


















