长话短说 ¶
我刚刚发布了 jub0bs/cors,一个新的 跨域资源共享 中间件库 去,也许是迄今为止最好的一个。 与更流行的产品相比,它有一些优点 RS/CORS 图书馆,包括
这是客户端代码的代表性示例:
package main
import (
"io"
"log"
"net/http"
"github.com/jub0bs/cors"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /hello", handleHello) // no CORS on this
corsMw, err := cors.NewMiddleware(cors.Config{
Origins: []string{"https://example.com"},
Methods: []string{http.MethodGet, http.MethodPost},
RequestHeaders: []string{"Authorization"},
})
if err != nil {
log.Fatal(err)
}
corsMw.SetDebug(true) // optional: turn debug mode on
api := http.NewServeMux()
api.HandleFunc("GET /users", handleUsersGet)
api.HandleFunc("POST /users", handleUsersPost)
mux.Handle("/api/", http.StripPrefix("/api", corsMw.Wrap(api)))
log.Fatal(http.ListenAndServe(":8080", mux))
}
func handleHello(w http.ResponseWriter, _ *http.Request) {
io.WriteString(w, "Hello, World!")
}
func handleUsersGet(w http.ResponseWriter, _ *http.Request) {
// omitted
}
func handleUsersPost(w http.ResponseWriter, _ *http.Request) {
// omitted
}
如果您已经确信并希望将代码迁移到
jub0bs/cors 话不多说,直接跳到 迁移指南 这篇文章的进一步内容。
为什么你应该更喜欢 jub0bs/cors ¶
RS/CORS 值得称赞的是,它是 Go 中最受欢迎的 CORS 中间件库。 它的开发仍在进行中,历时近十年,直到今天, 许多开源项目 依赖它。 但它完美吗? 当然,没有库,但我相信 Go 开发者应该得到最好的。 在我看来, RS/CORS 存在一些缺点
jub0bs/cors 地址; 请允许我详细介绍其中的几个。
更简单的API ¶
如果您查阅以下文档 RS/CORS,您很快就会意识到该库提供了至少四种方法来指定所需的 CORS 中间件应允许哪些 Web 来源:
|
|
如此大量的冗余选项不仅令人难以承受,而且正如之前的文章中提到的,其中一些选项很容易被误用。 相比下, jub0bs/cors 提供了一种配置所需 CORS 中间件任何特定方面的单一方法:
|
|
更多深奥的选项隐藏在一个名为的单独的结构类型中 ExtraConfig
。 所以, jub0bs/cors的 API 更容易理解,并且更适合自动完成。
作为奖励,并且 与 rs/cors 相反,
jub0bs/cors 允许您将整个 CORS 配置编组到 JSON 或 YAML,或者从 JSON 或 YAML 编组/取消编组。
更好的文档 ¶
我特别用心去写 准确且有用的文档
为了 jub0bs/cors。 尤其, 最近添加的增强型路由模式
在 网络/http 值得澄清; 正确应用 CORS 中间件(无论哪个库生成它)并结合使用 “方法齐全”模式 一开始确实很有挑战性。 我自己当然也很困惑,直到 卡拉娜·约翰逊
帮助我意识到 那 http.ServeMux
成分是关键。 为了避免用户 jub0bs/cors 类似的困惑,我已经包括在内 举例说明 在文档中。
最重要的是,尽管 jub0bs/cors 玩得最好
网络/http的路由器,我已经发布了涉及第三方路由器的示例(例如 志, 回声, 和 纤维) 在 一个单独的 GitHub 存储库。
广泛的配置验证 ¶
在之前的博客文章和
我在 GopherCon Europe 2023 上发表的演讲,我认为缺乏配置验证是大多数人难以解决 CORS 错误的主要原因之一。 不幸的是,一年后, RS/CORS 在这方面没有改善; 考虑以下代码示例:
import "github.com/rs/cors"
func main() {
corsMw := cors.New(cors.Options{
AllowedOrigins: []string{
"https://example.org",
",
},
AllowedMethods: []string{
"CONNECT",
"RÉSUMÉ",
},
AllowedHeaders: []string{"auth orization"},
})
// rest omitted for brevity
}
您能发现所需中间件的 CORS 配置存在问题吗? 也许你可以(经过一些审查)但是 RS/CORS 它本身不能,只是因为它几乎不执行 CORS 配置的验证。 相反,它非常乐意生产功能失调的中间件(这可能会给您带来很大的挫败感)或不安全的中间件(这将使您的用户面临风险)。
不像 RS/CORS, jub0bs/cors 执行广泛的配置验证,以防止您创建功能失调或不安全的 CORS 中间件:
import (
"fmt"
"os"
"github.com/jub0bs/cors"
)
func main() {
corsMw, err := cors.NewMiddleware(cors.Config{
Origins: []string{
"https://example.org",
",
},
Methods: []string{
"CONNECT",
"RÉSUMÉ",
},
RequestHeaders: []string{"auth orization"},
})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// rest omitted for brevity
}
上面的程序失败(理应如此),并显示一条错误消息,提醒您 CORS 配置的所有问题:
cors: forbidden method name "CONNECT"
cors: invalid method name "RÉSUMÉ"
cors: invalid request-header name "auth orization"
cors: for security reasons, origin patterns like " that
encompass subdomains of a public suffix are by default prohibited
调试模式 ¶
大多数 CORS 中间件库倾向于忽略所有 CORS 标头来响应失败的预检请求。
RS/CORS 行为如下; 和 jub0bs/cors 至少在默认情况下也是如此。
一方面,这种行为遵循良好的安全实践:CORS 中间件理想情况下应尽可能少地透露其配置(例如 允许的来源,
允许的方法等)在预检失败时向潜在对手发送信息。 另一方面,正如之前的文章中所解释的,这种行为严重阻碍了 CORS 问题的故障排除:浏览器没有足够的有关预检失败的信息,最终会引发一个错误,其消息掩盖了 CORS 问题的根本原因。
通常,您会收到如下错误消息:
访问获取地址
从原点已被 CORS 策略阻止:对预检请求的响应未通过访问控制检查:否
Access-Control-Allow-Origin
标头存在于所请求的资源上。 如果不透明响应满足您的需求,请将请求的模式设置为no-cors
在禁用 CORS 的情况下获取资源。
很困惑,你去仔细检查服务器的 CORS 配置,你发现 是 事实上,那里被列为允许的来源……🤔
最后,经过几个小时毫无进展,您发现了 CORS 问题的根本原因:服务器的配置不够宽松,因为客户端的请求包含一些标头(Authorization
,比如说)这恰好没有被明确允许,但应该是。 🤬
在我看来,CORS 中间件的这种行为是 CORS 错误因故障排除困难且耗时而臭名昭著的主要原因之一。
RS/CORS 摆脱困难 让用户指定一个记录器作为其中间件配置的一部分。 然后,该记录器为中间件处理的每个请求发出一条信息性消息:
2024/04/23 13:40:12 Handler: Preflight request
2024/04/23 13:40:12 Preflight aborted: headers '[Authorization]' not allowed
2024/04/23 13:40:13 Handler: Preflight request
2024/04/23 13:40:13 Preflight aborted: method 'PUT' not allowed
2024/04/23 13:40:14 Handler: Preflight request
2024/04/23 13:40:14 Preflight aborted: origin ' not allowed
2024/04/23 13:40:15 Handler: Actual request
2024/04/23 13:40:15 Actual request no headers added: missing origin
2024/04/23 13:40:17 Handler: Actual request
2024/04/23 13:40:17 Actual request no headers added: missing origin
2024/04/23 13:40:18 Handler: Actual request
2024/04/23 13:40:18 Actual response added headers: map[Access-Control-Allow-Origin:[ Vary:[Origin]]
这种方法确实简化了故障排除,但远非理想:您可以想象这样的记录器在重负载下在支持 CORS 的服务器上产生多少噪音……🤢
jub0bs/cors 采用不同的方法:它的 CORS 中间件提供了调试模式,您可以通过 (*Middleware).SetDebug
方法:
|
|
调试模式打开后,会覆盖上述中间件的行为,并包含更多信息来响应预检请求,甚至是失败的请求。 打开调试模式本质上会将您的 CORS 中间件变成一个
“浏览器低语者”:通过为浏览器提供足够的上下文信息,中间件能够从中引出错误消息,您实际上会发现这些消息对解决 CORS 问题很有帮助。 因此,我强烈建议您在遇到令人困惑的 CORS 问题时激活此调试模式。
可是等等; 还有更多! 因为 SetDebug
方法是 并发安全,您可以随意切换调试模式 在飞行中,即使您的服务器正在运行并且 CORS 中间件正在处理请求(即无需停止服务器、编辑其源代码,然后重新启动服务器)。 您所要做的就是以某种方式公开切换调试模式的能力; 例如,我修改了本文顶部的程序,添加了 /debug
切换调试模式的端点:
|
|
请注意,在实践中,就像
你不应该公开暴露你的 pprof 端点,您不应该向全世界公开公开切换此调试模式的能力。 因此,在上面的例子中,我已经包装了我的 setDebug
一些处理程序
授权 中间件。 另一种方法是限制对 /debug
反向代理级别的端点。
细心的读者可能会反对我的 Fearless CORS 设计理念 jub0bs/cors的调试模式似乎违反了原则11:
保证配置的不变性。
但我会反驳说,打开调试模式只会稍微改变中间件的行为,以简化故障排除; 切换调试模式不会修改允许的来源、允许的方法、允许的请求标头、最大年龄等集。此类配置修改仍然需要重新启动服务器。 😇
更强的性能保证 ¶
全面的, jub0bs/cors 和现代版本的 RS/CORS
有 相似的性能特征。 然而,有一种特定情况 RS/CORS v1.10.1 的表现很糟糕,以至于达到了便利的程度 拒绝服务。 针对一些冒充的恶意请求
CORS-预检请求,
RS/CORS 中间件确实分配了大量的内存,数量级超过 jub0bs/cors 某些情况下的中间件:
goos: darwin
goarch: amd64
pkg: github.com/jub0bs/cors-benchmarks
cpu: Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz
│ rs-cors │ jub0bs-cors │
│ sec/op │ sec/op vs base │
malicious_ACRH 17238.0n ± 3% 438.2n ± 5% -97.46% 😱
│ rs-cors │ jub0bs-cors │
│ B/op │ B/op vs base │
malicious_ACRH 37832.0 ± 0% 928.0 ± 0% -97.55% 😱
在我能想到的最坏(但现实的)情况下,1 Mib 的单个恶意请求会导致 RS/CORS 中间件分配了巨大的 116 MiB!
攻击者可能会滥用此行为,在服务器运行时(内存分配器和垃圾收集器)上产生过度负载。 当然,这种攻击媒介并不像以下那样严重: 重做服务,大多数 WAF 可能会阻止这些恶意请求,但这仍然应该引起关注。 特别是,由于 CORS 中间件通常必须位于任何身份验证逻辑之前,因此攻击者甚至不需要进行身份验证。
发现这个问题后 RS/CORS v1.10.1,我赶紧打开 GitHub 问题 #170
并发送了修复 拉取请求 #171。 我的拉取请求最终被合并并生成了一个新版本(v1.11.0)已发布,但仅在我提交问题一个月后。 无论漏洞的实际严重程度如何,维护人员对问题长期不解决的容忍度令人担忧。 😟
许多开源项目依赖于 RS/CORS v1.10.1(甚至更旧的版本)可能会受到影响 问题 #170。 其中之一, 普罗米修斯警报管理器,被宣传为一个正常运行的程序
需要不超过 50 Mib 的内存。 为了评估影响,我进行了一项测试,其中我同时向运行有以下命令的 Alertmanager 的 Dockerized 实例发送了几个恶意请求: 内存限制 50 Mib; 结果,Docker 容器很快就耗尽了内存并死掉了。 💀
因为 jub0bs/cors 由于采用防御性方法,因此不会受到此类问题的影响,并且表现出可预测的性能特征。
相比 jub0bs/cors 更青睐 rs/cors 的原因 ¶
尽管所有的 jub0bs/cors天哪,你可能还有坚持下去的正当理由 RS/CORS v1.11.0+,至少目前是这样。 这是我能列出的最详尽的清单:
- 由于某种原因,您还不能迁移到 Go v1.22(其 语义学 假设为 jub0bs/cors)。
- 您希望允许其方案既不是的 Web 来源
http
也不https
。 - 您需要比以下支持的更灵活的原始模式
jub0bs/cors。 - 您需要即时修改 CORS 中间件的配置,而无需重新启动服务器。
- 您希望为 CORS 中间件处理的每个请求记录一个事件。
如果这些项目都不能描述您目前的情况,我鼓励您迁移到 jub0bs/cors 尽快地。 😇
迁移指南 ¶
如果您准备好从 RS/CORS 到 jub0bs/cors,我希望您会发现这种迁移很简单。 本文的以下小节重点介绍了两个库之间的相似点和差异。 如果您仍然难以迁移项目,请随时询问我(在 乳齿象)寻求指导甚至拉取请求。
在下面的所有示例中,变量名为 handler
出现时,该变量被假定为类型 http.Handler
并在别处声明。
安装 jub0bs/cors ¶
开始取决于 jub0bs/cors
只需在项目中运行以下 shell 命令:
go get github.com/jub0bs/cors
一旦你直接依赖 jub0bs/cors
并且不再开启 RS/CORS,不要忘记通过运行以下命令来整理您的模块:
配置 CORS 中间件 ¶
基本配置结构类型具有不同的名称:
RS/CORS是 Options
, 然而 jub0bs/cors是
Config
。 这些结构类型字段的名称也不同; 两个库中对应字段的映射关系如下表所示:
RS/CORS | jub0bs/cors |
---|---|
AllowedOrigins |
Origins |
AllowOriginFunc |
不适用 |
AllowOriginRequestFunc |
不适用 |
AllowOriginVaryRequestFunc |
不适用 |
AllowedMethods |
Methods |
AllowedHeaders |
RequestHeaders |
ExposedHeaders |
ResponseHeaders |
MaxAge |
MaxAgeInSeconds |
AllowedCredentials |
Credentialed |
AllowPrivateNetwork |
ExtraConfig.PrivateNetworkAccess |
OptionsPassthrough |
不适用 |
OptionsSuccessStatus |
ExtraConfig.PreflightSuccessStatus |
Debug |
N/A(但请参阅调试模式) |
Logger |
不适用 |
而且, jub0bs/cors的 ExtraConfig
结构类型
允许您解锁上表中未提及的其他选项。
创建中间件并将其应用于处理程序 ¶
生成 CORS 中间件的函数也有不同的名称:
RS/CORS的名称为 New
, 然而 jub0bs/cors的名称为 NewMiddleware
。
除了, jub0bs/cors的 NewMiddleware
返回的,不仅仅是一个CORS中间件,更是一个 error
。 请检查一下 error
结果; 仅当其值为非nil
您可以假设生成的中间件是可用的吗?
最后,将中间件应用于
http.Handler
被(令人困惑地)命名 Handler
在 jub0bs/cors。
// jub0bs/cors
corsMw, err := cors.NewMiddleware(cors.Config{ /* omitted */ })
if err != nil {
// corsMw is unusable; bail out.
log.Fatal(err)
}
handler = corsMw.Wrap(handler) // wrap corsMw around handler.
默认配置 ¶
相比之下 RS/CORS,并且出于其他地方解释的充分理由,
jub0bs/cors 不提供默认的 CORS 配置。 如果要迁移的代码依赖于 RS/CORS的 Default
或者 AllowAll
函数,下面的代码片段说明了如何调整您的代码以迁移到 jub0bs/cors:
// rs/cors
handler = cors.Default().Handler(handler)
相当于
// jub0bs/cors
corsMw, err := cors.NewMiddleware(cors.Config{
Origins: []string{"*"},
Methods: []string{
http.MethodGet,
http.MethodHead,
http.MethodPost,
},
RequestHeaders: []string{
"Accept",
"Content-Type",
"X-Requested-With",
},
})
if err != nil {
// omitted: bail out, somehow
}
handler = corsMw.Wrap(handler)
和
// rs/cors
handler = cors.AllowAll().Handler(handler)
相当于
// jub0bs/cors
corsMw, err := cors.NewMiddleware(cors.Config{
Origins: []string{"*"},
Methods: []string{
http.MethodHead,
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
},
RequestHeaders: []string{"*"},
})
if err != nil {
// omitted: bail out, somehow
}
handler = corsMw.Wrap(handler)
关于 jub0bs/cors 的起源 ¶
对其前身的反思 ¶
大约一年前,在意识到开发人员的麻烦之后
跨域资源共享 (CORS) 我发布的主要可以归咎于工具而不是其用户 jub0bs/fcors,Fearless CORS 的参考实现,我的 CORS 中间件库的设计理念,
就个人而言,这个原始库被证明是一个思想的形成实验室,不仅涉及库设计,还涉及算法和数据结构(尤其是 基数树):
🚀 发布 jub0bs/fcors v0.8.0,我的 CORS 中间件库 #golang! 得益于专门的基数树,
– 中间件初始化现在分配更少,🪶
– 原点匹配现在更快了! ⚡️https://t.co/YMhPUbpTAa– @[email protected](也jub0bs.bsky.social)(@jub0bs) 2024 年 2 月 8 日
然而,尽管 jub0bs/fcors 获得好评 开发商, OWASP, 还有一些 WHATWG 各位(在私人交流中),令人失望的是,它的采用仍然有限。 例如,在撰写本文时,该项目的 GitHub 存储库仅累积了
区区 79 个观星者; 没有什么可嘲笑的,但还远未取得巨大的成功。 相比下, RS/CORS
拥有 超过 2,500 名观星者。
如果被迫推测原因 jub0bs/fcors由于采用率低迷,我首先认为,一个有竞争力的软件库,无论其优点如何,不太可能迅速上升到与现有库一样高的受欢迎程度。 其次,我会引用一些有争议的设计决策 jub0bs/fcors。 该库确实严重依赖 功能选项,Go 社区中备受诟病的模式。 赢得这种模式的坚定反对者总是一场艰苦的战斗,而且
我在 2023 年 GopherCon Europe 上就该主题发表的演讲虽然很受欢迎,但并没有影响他们。 在过去的几个月里,我自己对这种模式的看法有所改变。 我仍然相信它在某些情况下很有用,但它的一些痛点对我来说变得更加明显。
与其让我泄气,不如 jub0bs/fcors不引人注目的采用促使我编写了一个精神继承者:一个新的 Go CORS 库,它遵循 Fearless CORS 的原则,但其更传统的 API 具有普遍吸引力的承诺: jub0bs/cors。
为什么不为 rs/cors 做出贡献呢? ¶
最后,我应该解决房间里的大象:为什么要创建一个竞争库? 为什么不贡献改进 RS/CORS 反而? 答案并不像看起来那么简单。
虽然我能找出设计上的错误 jub0bs/fcors,我几乎不会改变我的 Fearless CORS 设计理念。 我仍然坚信它的 12 条原则是合理的,值得传播到其他 CORS 库(甚至是 Go 生态系统之外的库)。 怀着这个雄心壮志,我确实做出了贡献 问题
和 拉取请求 到 RS/CORS,其中大部分 奥利维尔·普瓦特雷,图书馆的维护者,请修复或合并。 此外,还有 毫无疑问 那 jub0bs/fcors
激发奥利维尔改进的方面 RS/CORS的表现。
然而,并没有掌舵 RS/CORS,我对它的发展影响力有限; 事实上,这个库与 Fearless CORS 所传达的理想,即易于使用且不易误用的 CORS 库还有很大差距。 例如,多个钩子(例如 AllowOriginFunc
) 那
RS/CORS 在我看来,这些规定是危险的错误特征。 如何改进库的这方面并不明显; 事实上,还有一个这样的钩子 最近潜入了 API。 弃用所有这些钩子将是一个很好的第一步,但完全删除对它们的支持将构成重大变化。 也许是一个假设 v2 的 RS/CORS
可以使该库与 Fearless CORS 保持一致,但我不清楚 Olivier 是否计划在不久的将来发布新的主要版本。
如果我不能弯曲 RS/CORS 变成接近我理想的 CORS 中间件库的形状,我至少可以尝试用更好的库取代它。 这是我的志向 jub0bs/cors。
致谢 ¶