Day 13 / 共 20 天 · 第 3 周 Wasm 插件
写一个插件(key-auth)
昨天(Day 12)把 SDK 的骨架(SetCtx/三级 Context/生命周期)过了一遍;今天把它用起来——逐行读一个真实的认证插件 key-auth(API Key 鉴权)。它是典型的 auth 插件,展示"配置解析 + 请求头处理 + 拦截"的完整套路,比 hello-world 实用得多。
📍 你在第 3 周(Wasm 插件)的位置
D11 插件概览→
D12 wasm-go SDK→
D13 写插件 key-auth→
D14 实战插件→
D15 golang-filter
🤔 痛点:Day 12 学了 SDK 的一堆概念,但"真写一个有用的插件"到底长啥样?
hello-world 只会加个头、回句 "hello world",玩具而已。真实插件要读配置、按规则匹配、拦非法请求、处理各种边界情况——这些拼在一起是什么样子?今天用一个天天在用的认证插件 key-auth 给你走一遍完整套路。
💡 先兜住今天(延续"门口保安"世界观)
key-auth 就是门口那位查证件的保安:访客递上"门禁卡"(API Key,藏在请求 header 或 query 里),保安拿着手里的花名册(
credential2Name 反查表)一查——卡有效就放行、并在访客背上贴张"这是张三"的标签(X-Mse-Consumer 头)给楼里同事看;卡无效或没带卡,当场请回(返回 401/403)。今天就看这位保安从"读花名册(配置)"到"查卡放行/拦截"的完整工作流程。L01
key-auth 是什么
extensions/key-auth/main.go(404 行)——API Key 认证:检查请求带的 key 是否合法,合法放行并标记调用方身份,非法拦截返回 401/403。
API Key 鉴权 = "查通行证"
很多 API 用 API Key 认证:客户端在 header(或 query)带一个 key,网关检查 key 是否有效。key-auth 插件就干这个:从请求里取 key → 查合法 key 列表 → 合法则加个
X-Mse-Consumer header 标记"这是谁"、放行;非法则直接返回 401(没带 key)/403(key 错)。这是网关最常见的能力之一。读它能学会"一个真实插件从配置到拦截的完整套路"——比 hello-world 实用得多。L02
注册回调
// key-auth/main.go:38 init 注册两个回调
wrapper.SetCtx("key-auth",
wrapper.ParseOverrideConfigBy(parseGlobalConfig, parseOverrideRuleConfig), // 全局+规则配置解析
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), // 处理请求头
)
读法:注册两样:①
ParseOverrideConfigBy——两个函数(全局配置解析 + 规则级覆盖解析,配合 Day 12 的 RuleMatcher 多级配置);②ProcessRequestHeadersBy——请求头到达时的处理逻辑。key-auth 只需处理请求头(鉴权在头阶段就能完成,不用看 body)。L03
配置结构 + 注解
// key-auth/main.go:95 KeyAuthConfig(含大量 @Title/@Description/@Scope 注解)
// :67 顶部:@Name key-auth / @Category authn / @Phase AUTHN / @Priority 321
type KeyAuthConfig struct {
consumers []Consumer // 合法的 key 及其身份
keys []string // key 在哪个 header/query 名
inQuery, inHeader bool // key 从 query 还是 header 取
// …
}
注解自动生成"配置表单"
注意配置结构体上大量
@Title/@Description/@Scope GLOBAL 注解。Higress 用这些注解自动生成插件市场的配置表单和文档——用户在 Console(下一站)里看到的插件配置界面就是从这些注解生成的。顶部的 @Phase AUTHN/@Priority 321 声明"这是个认证阶段插件、优先级 321"(对应 Envoy 课 Day 17 的 WasmPhase/WasmPriority——认证插件在别的插件之前跑)。这是"代码即文档即 UI"的实践——注解一处定义,多处复用。L04
parseGlobalConfig
// key-auth/main.go:140 parseGlobalConfig
// 校验 keys、in_query/in_header、consumers
// 构建 credential2Name 反查表(key → consumer 名字)
读法:解析全局配置:校验必填项,把"合法 key 列表"预处理成
credential2Name 反查表(key → 是谁)。预建反查表是性能优化——运行时鉴权直接查表 O(1),不用遍历。你的 ParseConfig 回调(Day 12)就长这样——把 JSON 配置转成运行时高效的结构。L05
覆盖配置继承
// key-auth/main.go:245 parseOverrideRuleConfig
*config = global // 先继承全局配置
// 再解析路由级 allow 列表(这个路由允许哪些 consumer)
规则级配置"继承"全局
Day 12 讲了 RuleMatcher 的多级配置。这里体现"继承":路由级配置先
*config = global(拷贝全局配置作基础),再叠加这个路由特有的设置(比如"这个路由只允许 consumer A、B 访问")。于是全局定义"有哪些合法 key",路由级定义"这个路由允许哪些 key"——分层且不重复。这就是 Day 12 的 ParseOverrideConfig(全局 + 规则两个函数)的实际用法。L06
onHttpRequestHeaders
// key-auth/main.go:278 onHttpRequestHeaders
// 从 header/query 取 token(:299)
// credential2Name 校验(:327)
// 通过 → AddHttpRequestHeader("X-Mse-Consumer", name)(:333,标记身份)
// 失败 → SendHttpResponseWithDetail 返回 401/403(:368,带 WWW-Authenticate 头)
读法:鉴权主流程:取 key → 查反查表 → 合法则加
X-Mse-Consumer header(告诉下游"这是谁")并 ActionContinue(放行);非法则 SendHttpResponseWithDetail 直接返回 401/403(拦截,不转发上游,对应 Envoy 课 Day 08 的 sendLocalReply)。这就是网关"当场拦截非法请求"的实现。图注:鉴权在"请求头阶段"就能拍板——放行/拦截,不用等 body。放行才转上游,拦截直接回响应。
📝 举个例子:两次请求,一次放行一次拦截
合法花名册
① 请求带
② 请求带
credential2Name = {"abc123": "张三", "xyz789": "李四"}。① 请求带
X-Api-Key: abc123 → 查到"张三" → 加头 X-Mse-Consumer: 张三 → 放行,上游服务从这个头知道"来的是张三"。② 请求带
X-Api-Key: hack000 → 花名册里没有 → SendHttpResponseWithDetail(403) → 拦截,请求根本到不了上游。L07
三态鉴权语义
global_auth 的三态(真实工程细节)
key-auth 有个
global_auth 配置的三态语义(main.go:266-277 注释是极好的教学材料):①true——全局强制鉴权(所有路由都要 key);②false——只有显式配了 allow 的路由才鉴权;③未设(nil)——兼容模式,按是否有 consumer 配置决定。为什么要三态?因为要平滑兼容"老配置"和"新配置"、"全局开"和"按路由开"多种使用习惯。用 Go 的指针(nil 表示"未设")区分"设成 false"和"没设"。这种"三态布尔"是配置系统处理"默认 vs 显式"的常见手法(Istio 课 Day 19 的 BoolValue 也是)。读懂它,你就理解了真实插件如何处理配置的边界情况。global_auth 的值 | 类比(全楼门禁策略) | 效果 |
|---|---|---|
true | 整栋楼所有门都强制刷卡 | 所有路由都要鉴权 |
false | 只有贴了"需刷卡"告示的门才查 | 仅显式配了 allow 的路由鉴权 |
| nil(没设) | 没下总政策,看各门自己有没有装读卡器 | 兼容模式:按是否有 consumer 配置决定 |
⚠️ 常见误解:很多人以为"布尔就非真即假两种",于是
false 和"没设"会被当成一回事——那就错了。这里用 Go 指针 *bool:nil 代表"运维压根没写这个字段",和显式写 false 语义不同。分清"没设"与"设成 false",才能做到平滑兼容老配置。L08
今日小结 + 动手
🧠 今天你应该能回答
- key-auth 干什么?合法/非法各怎么处理?
- 它注册了哪两个回调?
- 配置结构的注解有什么用(自动表单)?
- parseGlobalConfig 为什么建反查表?规则级怎么继承全局?
- global_auth 三态各是什么?为什么用指针区分?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress/plugins/wasm-go/extensions/key-auth
sed -n '38,42p' main.go # 注册
sed -n '266,340p' main.go # 三态语义 + 鉴权主流程
sed -n '67,95p' main.go # 注解 + 配置结构
明天预告 · Day 14:实战插件——看更复杂的插件:Redis 分布式限流(cluster-key-rate-limit,用 leader 选举 + Redis)、transformer(改 header/body),以及插件的单测。