zitadel 不能直接作为 gin 或 net/http 的中间件挂载,因其是独立运行的 oauth2/oidc 认证服务;集成需网关自行实现 token 校验逻辑,通过 introspection endpoint 或 jwks+jwt 方式验证,而非嵌入 zitadel 进程。

Zitadel 不能直接作为 Gin 或 net/http 的“中间件”挂载——它是个独立的 OAuth2/OIDC 认证服务,Golang 网关要集成它做集中鉴权,核心动作是:在请求到达业务 handler 前,用 Zitadel 的 introspection endpoint 或 JWKS + JWT 校验逻辑做 token 验证,而非把 Zitadel 进程嵌进你的代码里。
为什么不能直接 “use Zitadel middleware”
Zitadel 是一个运行在独立地址(如 https://auth.example.com)的 OIDC 提供方,不提供 Go SDK 中间件包。所谓“集成”,本质是:网关自己实现 token 校验逻辑,调用 Zitadel 的标准接口完成验证。误以为有现成 ZitadelMiddleware() 函数可用,是新手最常踩的坑。
- Zitadel 不暴露
/oauth2/token/introspect给所有客户端——需提前在 Zitadel 控制台为你的网关 client 配置 scope 和 token introspection 权限 - 若用 JWT 直接校验(推荐),必须从 Zitadel 的
/.well-known/jwks.json动态获取公钥,并缓存 JWKs(避免每次请求都 HTTP GET) - Zitadel 发放的 access_token 默认是 opaque token(不可解析),必须显式开启 JWT 格式(在 Zitadel client 配置中勾选 “Use JWT Access Token”)
- introspection 返回的 JSON 字段名是
active(布尔值)、scope、client_id,不是标准 OAuth2 RFC 的全部字段,别硬套其他 provider 的解析逻辑
用 JWKS + jwt.ParseWithClaims 实现无状态校验
这是生产环境首选方式:不依赖 Zitadel 实时响应,延迟低、可水平扩展。前提是 Zitadel client 已启用 JWT access token。
- 启动时或首次请求时,GET
https://auth.example.com/oauth/v2/keys获取 JWKs,用github.com/golang-jwt/jwt/v5的jwt.WithKeySet()构建验证器 - 从
Authorization: Bearer <token></token>提取 token 后,调用jwt.ParseWithClaims(tokenStr, &CustomClaims{}, keyFunc),其中keyFunc应根据kid从缓存 JWKs 中查对应公钥 -
CustomClaims必须嵌入jwt.RegisteredClaims,否则exp、iss不会自动校验;iss必须严格比对为https://auth.example.com(注意末尾斜杠) - 校验通过后,用
c.Set("zitadel_user_id", claims.Subject)或c.Set("zitadel_scopes", claims.Scope)透传,下游 handler 才能做 RBAC 判断
用 token introspection 做带状态的细粒度控制
适合需要实时吊销、动态 scope 变更、或 Zitadel client 未启用 JWT 的场景。但每次请求都需额外一次 HTTPS 调用,延迟和失败率必须考虑。
- 构造 POST 请求到
https://auth.example.com/oauth/v2/introspect,body 为url.Values{"token": {tokenStr}, "token_type_hint": {"access_token"}},header 带Authorization: Basic <base64></base64> - 必须检查响应 status == 200 且 JSON 中
active为 true;scope字段是空格分隔字符串(如"api:read api:write"),需strings.Fields()拆分 - introspection 响应不含用户 identity 信息(如 email、name),若业务需要,得额外调用
/userinfoendpoint,且需 client 授权openidscope - 务必设置超时(建议 ≤ 300ms)和重试(最多 1 次),失败时按策略 fallback(如拒绝 or 放行),别让 Zitadel 故障拖垮整个网关
透传用户身份与权限时容易漏掉的关键点
鉴权成功只是第一步;后续路由、handler、日志、审计全靠你透传的字段驱动。这里错一点,越权就可能绕过。
- 别用
c.Set("user", map[string]interface{}{...})存原始 claims——类型断言易 panic,改用结构体type ZitadelUser struct { Subject string; Email string; Scopes []string } - RBAC 判断逻辑必须放在另一个中间件里(比如
RBACMiddleware()),且挂载顺序必须在 Zitadel 鉴权之后;否则c.Get("zitadel_scopes")是 nil - Zitadel 的 scope 是粗粒度的(如
api:admin),具体接口级权限(如POST /v1/orders)仍需你自己的权限表或策略引擎(OPA、Casbin)配合 - 所有透传字段名统一用小写+下划线(
"zitadel_user_id"),避免下游 handler 因大小写问题取不到值
真正难的不是调通 Zitadel 接口,而是把 token 校验、JWKS 缓存刷新、scope 解析、上下文透传、fallback 策略这五件事串成一条不丢不乱不阻塞的链。少一个环节,要么 401 泛滥,要么越权静默通过。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











