本文详解 Fetch API 报错“No 'Access-Control-Allow-Origin' header is present”的根本原因及解决方案,重点指出 Access-Control-Allow-Origin: * 在含凭证请求下的失效机制,并提供 Go 服务端安全、合规的 CORS 响应配置方法。
本文详解 fetch api 报错“no 'access-control-allow-origin' header is present”的根本原因及解决方案,重点指出 `access-control-allow-origin: *` 在含凭证请求下的失效机制,并提供 go 服务端安全、合规的 cors 响应配置方法。
在使用现代前端框架(如 React/Redux)配合 fetch API 调用 Go 后端时,开发者常遇到如下错误:
Fetch API cannot load http://localhost:8100/districts/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8200' is therefore not allowed access.
该错误并非前端代码问题,而是浏览器强制执行的 CORS(跨域资源共享)安全策略所触发。关键在于:*`Access-Control-Allow-Origin: ""` 并非在所有场景下都有效**。
⚠️ 根本原因:* 的限制条件
根据 Fetch 规范 和主流浏览器实现(Chrome、Firefox、Safari),当响应头中包含以下任一敏感标头时,Access-Control-Allow-Origin *不得使用通配符 ``**:
- Access-Control-Allow-Credentials: true
- Set-Cookie(即服务端尝试设置 Cookie)
- 任意自定义认证头(如 Authorization)
而 fetch 默认不携带凭据(credentials),此时 * 是允许的;但一旦你显式启用凭据(如 fetch(url, { credentials: 'include' })),或后端无意中设置了 Set-Cookie,浏览器将立即拒绝 *,要求精确匹配 Origin。
更隐蔽的是:即使你的 Go 处理函数中先写了 w.Header().Set("Access-Control-Allow-Origin", "*"),后续任何对 w.Header() 的修改(包括 w.WriteHeader() 或写入响应体)都可能使该 header 生效失败——Go 的 http.ResponseWriter 对 header 的写入时机极为敏感。
✅ 正确做法:动态反射 Origin(推荐)
最稳妥、符合规范的方式是读取请求中的 Origin 头,并原样回写为 Access-Control-Allow-Origin 值(需校验白名单以保安全):
func getDistricts(w http.ResponseWriter, r *http.Request) {
// ✅ 1. 先获取并校验 Origin(生产环境务必白名单校验!)
origin := r.Header.Get("Origin")
allowedOrigins := map[string]bool{
"http://localhost:8200": true,
"https://myapp.com": true,
}
if allowedOrigins[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
// ✅ 2. 若需携带 Cookie 或 Authorization,请同时启用凭据支持
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
// ✅ 3. 设置 Content-Type(注意:应在设置 CORS header 之后,且不能重复 Set)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// ✅ 4. 允许特定 HTTP 方法(如需 POST/PUT)
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
// ✅ 5. 允许客户端读取的响应头(如 X-Total-Count)
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// ✅ 6. 显式处理预检请求(OPTIONS)
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
// ✅ 7. 正常业务逻辑(务必在 headers 设置完毕后执行)
w.WriteHeader(http.StatusOK)
rows, err := db.Query("SELECT * FROM districts")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ... 序列化 JSON 并写入 w
}? 重要提醒:
- Access-Control-Allow-Origin 必须在 w.WriteHeader() 和首次 w.Write() 之前设置;
- 若使用 json.Marshal + w.Write(),请确保 Content-Type 已提前设置;
- 生产环境严禁无条件使用 r.Header.Get("Origin"),必须校验来源白名单,否则等同于开放任意跨域访问;
- 若前端 fetch 需携带 Cookie,请同时设置 { credentials: 'include' },且后端 Access-Control-Allow-Credentials: "true" 与 Access-Control-Allow-Origin *不可为 ``**。
? 验证与调试技巧
- 使用 curl -I http://localhost:8100/districts/ -H "Origin: http://localhost:8200" 检查响应头是否包含正确的 Access-Control-Allow-Origin;
- 在 Chrome DevTools → Network → 查看请求的 Response Headers,确认 Access-Control-Allow-Origin 值是否与当前页面 Origin 完全一致(含协议、端口、路径);
- 若仍报错,检查是否遗漏 OPTIONS 预检响应(尤其当请求含自定义 header 或非简单 method 时)。
遵循以上配置,即可彻底解决 Fetch API 的跨域报错,同时兼顾安全性与浏览器兼容性。


















