为可信子域配置 CrossOriginProtection 放行规则
当控制台部署在 https://console.example.com,API 部署在 https://api.example.com 时,两者虽然属于同一站点,却不是同源。给 API 接入 http.CrossOriginProtection 后,控制台发出的 POST、PUT、PATCH、DELETE 请求需要用 AddTrustedOrigin 精确放行;最稳妥的规则是逐个登记完整的 scheme://host[:port],不要把整个 *.example.com 想当然地视为可信。
结论先说:为每个真实前端 Origin 建立精确白名单,在进程启动时完成格式校验,运行时只让通过来源检查的请求进入业务 Handler。新增子域必须显式变更配置并补测试。
官方文档:https://pkg.go.dev/net/http#CrossOriginProtection
场景:同站点子域不等于同源
一个常见拆分是官网、运营控制台和 API 各占一个子域。浏览器从控制台向 API 发起写请求时,Sec-Fetch-Site 可能是 same-site,但 CrossOriginProtection 的默认直接放行条件是 same-origin 或 none。这正是许多团队接入后遇到 403 的原因:域名看起来属于同一家公司,浏览器安全模型看到的却是两个 Origin。
这里不应该关闭保护,也不应该把所有子域一次性放开。子域可能由不同团队、不同应用甚至第三方平台承载,一处子域失陷不应自动获得向 API 提交写操作的能力。把控制台的完整 Origin 加入可信清单,才能把边界收敛到真正需要调用 API 的前端。
请求输入:先看保护器实际使用哪些信号
CrossOriginProtection 保护的是非安全方法的浏览器请求。官方文档说明,GET、HEAD、OPTIONS 始终允许,因此这些方法不能承担状态修改。对于其他方法,保护器主要读取 Sec-Fetch-Site,必要时再比较 Origin 主机与请求 Host;如果两个头都没有,当前实现将请求视为同源或非浏览器请求并允许。
可信子域规则只作用于带有 Origin 的请求,并要求 Origin 头与登记值完全一致。这意味着下面三项都属于规则的一部分:
- scheme:
http与https是不同 Origin; - host:
console.example.com与admin.example.com必须分别登记; - port:显式端口不同,也应按不同 Origin 管理。

配置校验:在启动阶段逐条登记可信 Origin
可信来源适合放在部署配置中,但不要在每次请求里解析字符串。下面的代码从逗号分隔配置读取 Origin,启动时逐条调用 AddTrustedOrigin;任何一条格式错误都直接终止启动,避免服务带着残缺白名单上线。
package main
import (
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
)
func newCrossOriginProtection(raw string) (*http.CrossOriginProtection, error) {
protection := http.NewCrossOriginProtection()
for _, item := range strings.Split(raw, ",") {
origin := strings.TrimSpace(item)
if origin == "" { // 跳过空配置项,避免把空字符串登记为规则
continue
}
if err := protection.AddTrustedOrigin(origin); err != nil {
return nil, fmt.Errorf("可信 Origin %q 无效: %w", origin, err)
}
}
protection.SetDenyHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 只记录判断所需的低敏感度字段,不记录 Cookie、令牌或请求体。
log.Printf("cross-origin denied method=%s path=%s origin=%q fetch_site=%q",
r.Method, r.URL.Path, r.Header.Get("Origin"), r.Header.Get("Sec-Fetch-Site"))
http.Error(w, "禁止跨源写入", http.StatusForbidden)
}))
return protection, nil
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("POST /api/orders", func(w http.ResponseWriter, r *http.Request) {
// 生产代码仍要在这里完成身份认证、权限校验、输入校验和持久化。
w.WriteHeader(http.StatusNoContent)
})
protection, err := newCrossOriginProtection(os.Getenv("TRUSTED_ORIGINS"))
if err != nil {
log.Fatal(err) // 配置错误时失败退出,避免静默缺少可信来源
}
server := &http.Server{
Addr: ":8080",
Handler: protection.Handler(mux), // 在业务路由之前统一执行来源检查
ReadHeaderTimeout: 5 * time.Second,
}
log.Fatal(server.ListenAndServe())
}
生产环境可以设置 TRUSTED_ORIGINS=https://console.example.com,https://admin.example.com。本地开发若使用 http://localhost:5173,应放在开发环境自己的配置里,不能混入生产白名单。AddTrustedOrigin 接受的是 Origin,而不是 URL 页面地址,因此不能包含路径、查询参数或片段。
存储模型:精确清单比通配符更容易审计
官方 API 的语义是 Origin 头与登记值精确匹配,并没有“可信主域名后缀”的配置含义。即使组织拥有 example.com,也不要尝试用 https://*.example.com 表达放行;需要访问 API 的控制台有几个,就登记几个完整 Origin。
这种写法看似多了几行配置,却带来三个好处:代码审查能看见新增信任关系;下线旧控制台时可以单独删除规则;端口或 scheme 变更不会在不知情的情况下继承权限。对于动态租户子域,如果数量很大,不应简单扩大 CSRF 白名单,而应重新评估调用方式、身份边界和令牌模型。

查询路径:一条写请求如何得到最终结果
把请求决策按顺序梳理后,排查 403 会简单很多:
- 如果方法是 GET、HEAD、OPTIONS,直接允许;
- 如果浏览器信号表明请求同源,允许进入业务 Handler;
- 如果 Origin 与某条可信 Origin 完全一致,允许进入;
- 如果请求直接命中显式绕过模式,则绕过检查;
- 其他非安全跨源浏览器请求进入拒绝 Handler,默认状态码为 403。
Handler 适合统一包装整个 mux。若必须把检查结果嵌入已有中间件,也可以调用 Check,但要注意它只返回错误,不会自动执行 SetDenyHandler。两种接法不要同时重复检查,以免日志和拒绝响应出现两次。
异常处理:可信 Origin、CORS 和路由绕过不要混用
| 能力 | 解决的问题 | 使用原则 |
|---|---|---|
AddTrustedOrigin | 允许明确的跨子域浏览器写请求 | 逐个登记完整 Origin |
| CORS 响应头 | 决定浏览器脚本能否读取跨源响应 | 按前端读取需求单独配置,不能替代 CSRF 防护 |
AddInsecureBypassPattern | 让直接命中特定 ServeMux 模式的请求全部跳过检查 | 仅用于有独立签名认证的机器接口等少数场景 |
SetDenyHandler | 统一 403 格式、指标和低敏感度日志 | 不回显内部规则,不记录 Cookie 或令牌 |
遇到合法请求被拒绝时,先确认浏览器发出的 Origin 是否与配置逐字一致,再检查 scheme、端口和环境配置。不要为了快速恢复而给整个 API 添加绕过模式。官方文档明确指出,AddInsecureBypassPattern 会允许所有直接匹配该 ServeMux 模式的请求,并且无效或冲突模式会触发 panic。
清理策略:用测试固定边界,用审计删除旧规则
白名单是会随部署变化的安全数据。每次新增 Origin 都应附上用途、负责人和下线条件;旧控制台迁移完成后及时删除对应规则。拒绝指标可以按路由、Origin 和 Sec-Fetch-Site 聚合,但要限制标签基数,并避免收集敏感字段。
至少应覆盖四组回归:同源写请求通过;登记的控制台 Origin 通过;未登记的兄弟子域得到 403;安全方法通过但不产生状态变化。另外再测一个无浏览器来源头的服务调用,确认它仍然依靠令牌或签名完成认证,而不是误把 CrossOriginProtection 当作 API 鉴权。
当可信子域数量开始快速增长时,先停下来检查架构。精确 Origin 清单的价值就在于让信任扩张可见;如果每个新租户都必须成为高权限浏览器来源,真正需要调整的往往是认证和调用模型,而不是继续堆白名单。
相关问题
能否写成 https://*.example.com? 不建议,也不符合 AddTrustedOrigin 的精确匹配语义。应登记每个完整 Origin。
同站点子域为什么还会被拦截? same-site 与 same-origin 不是同一个概念。跨子域浏览器写请求需要显式建立信任。
配置了可信 Origin 后还要配置 CORS 吗? 如果前端脚本需要读取跨源响应,仍要配置合适的 CORS;两者职责不同。
默认拒绝响应是什么? Handler 默认返回 403,也可以通过 SetDenyHandler 自定义响应和日志。
Linux pidfd 如何避免按 PID 操作进程的竞态
- 上一篇
- Linux pidfd 如何避免按 PID 操作进程的竞态
- 下一篇
- 反向代理后 CrossOriginProtection 误判来源怎么办
-
- Golang · Go教程 | 30分钟前 |
- 用 B.Loop 正确排除一次性初始化开销
- 117浏览 收藏
-
- Golang · Go教程 | 52分钟前 | go · testing · 基准测试 Go benchmark b.N testing.B.Loop
- testing.B.Loop 如何重写旧式基准测试循环
- 315浏览 收藏
-
- Golang · Go教程 | 1小时前 | Go net/http csrf CrossOriginProtection 表单接口
- Go CrossOriginProtection 如何保护表单写接口
- 245浏览 收藏
-
- Golang · Go教程 | 2小时前 | go · 插件 · 文件系统 · 路径遍历 符号链接 os.OpenRoot Go os.Root 插件文件读取 目录边界
- os.Root 怎样为插件读取建立目录边界
- 104浏览 收藏
-
- Golang · Go教程 | 2小时前 | go · 文件上传安全 tar.gz Go os.Root 归档展开 Zip Slip
- 用 os.Root 安全展开用户上传的归档文件
- 441浏览 收藏
-
- Golang · Go教程 | 3小时前 |
- Go os.Root 如何把文件操作限制在上传目录内
- 200浏览 收藏
-
- Golang · Go教程 | 3小时前 | uuid · Go教程 · 输入校验 · uuid.Parse Go uuid包 UUID请求校验 Go HTTP参数校验 UUID Nil校验
- 用 uuid 包校验外部请求中的标识符
- 290浏览 收藏
-
- Golang · Go教程 | 4小时前 | 标准库 · 数据库 · uuid · Go教程 · database/sql · Go标准库uuid uuid.New UUID数据库 BINARY(16) CHAR(36) uuid.Parse
- Go 标准库 uuid 如何生成并写入数据库字段
- 344浏览 收藏
-
- Golang · Go教程 | 4小时前 | Go教程 · 密码学 · runtime/secret secret.Do 前向保密 Go byte切片 敏感内存
- runtime/secret 与普通 byte 切片如何划分使用边界
- 137浏览 收藏
-
- Golang · Go教程 | 4小时前 | Go教程 · 内存转储 core dump secret.Do Go runtime/secret heap dump 密钥擦除
- 用 runtime/secret 降低内存转储中的密钥暴露
- 156浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 386次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 468次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 475次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 415次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 240次使用
-
- Go error wrapping 实战:别让错误日志只剩一句 failed
- 2026-06-01 151浏览
-
- Go pprof 排查慢接口:别只会看火焰图,先把问题问对
- 2026-06-01 101浏览
-
- Go Flight Recorder 实战:线上偶发卡顿,别再只靠日志碰运气
- 2026-06-01 323浏览
-
- Go testing/synctest 实战:别再用 time.Sleep 赌并发测试会过
- 2026-06-01 428浏览
-
- Go slog 生产实践:日志别只会打印 error,要能帮你排障
- 2026-06-01 143浏览

