生产部署清单
上线前逐项核对。每一项都对应 wicket v0.1.4 中真实存在的机制或已知限制;标注“宿主负责”的项目 wicket 不会替你处理。
网络与签发者
对外只提供 HTTPS。 wicket 不终止 TLS,由宿主进程(
ListenAndServeTLS)或前置代理负责。OIDC 的浏览器端点要求 TLS,服务端会话 cookie 的ServerSideSessions.CookieSecure默认就是true。显式设置
IssuerUri,与客户端、资源方配置的 authority 完全一致(含https://,不带末尾/)。未设置时签发者随请求的Host变化,令牌校验会在不同入口之间失败。处理反向代理头(宿主负责)。 wicket 不读取
X-Forwarded-*/Forwarded。IssuerUri只固定iss,发现文档里的各端点 URL 仍由r.Host与r.TLS推导——TLS 在代理层终止时,它们会变成http://内部地址。可在 wicket 外层加一个只信任你自己代理的中间件(已在 v0.1.4 实测,发现文档随之输出https://id.example.com/...):gofunc trustProxy(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 仅当请求确实来自你的代理时才这样做(例如校验来源网段) if r.Header.Get("X-Forwarded-Proto") == "https" { r.TLS = &tls.ConnectionState{} } if h := r.Header.Get("X-Forwarded-Host"); h != "" { r.Host = h } next.ServeHTTP(w, r) }) } handler := trustProxy(hosting.ScopeMiddleware(root)(srv.UseWicket()(app)))挂在站点根路径。 wicket 的 URL 计算不支持子路径前缀,见 嵌入宿主应用。
存储
- 不调用
cfg.AllowInMemoryFallback()。 它只用于开发环境。 - 注入全部 11 个存储 seam,并调用
cfg.Freeze(),让遗漏在启动时以 panic 暴露,而不是静默回退到内存。wicket-pg 提供其中 9 个,另外两个(授权参数消息存储、分布式缓存)需要宿主显式处理,见 持久化存储。 - 启动序列中执行
migrations.Up,并考虑用ConformsTo()校验适配器与 wicket 契约版本一致。 - 连接池由宿主关闭,
root.Close()不负责。
签名密钥
- 密钥跨重启、跨副本一致。 手动方式:所有副本加载同一把私钥(kid 用 RFC 7638 指纹,自然一致)。自动方式:所有副本共享同一个持久化的
keymgmt.RecordStore(如 wicket-pg 的NewKeyRecordStore)。 - 开启自动轮换时按 v0.1.4 的方式装配:自建
keymgmt.Manager并适配注入,修改opts.KeyManagement不生效。见 签名密钥。 - 私钥保护器必须真正加密。 自动管理时传入自己的
dp.DataProtectionProvider(如 AES-GCM),主密钥来自密钥管理系统,且所有副本相同。wicket 内置的默认数据保护实现不适用于生产。 - 保留期覆盖令牌寿命。
RetentionDuration(默认 14 天)应不短于最长的令牌寿命加时钟偏差;手动轮换时同理,旧公钥要在 JWKS 中留足时间。 - 签名私钥不进代码仓库和镜像,通过密钥文件挂载或 KMS 下发。
数据保护(已知限制)
- 了解内置数据保护的边界。 v0.1.4 容器内置的默认
DataProtectionProvider不适用于生产,容器也暂未提供全局替换它的注入点(KeyManagement.Protector只作用于密钥管理器;外部登录方案可按 外部登录 的做法单独传入自己的实现)。它用于 wicket 的消息 cookie、错误与登出等交互上下文、外部登录 state;另外,持久化授权数据在 v0.1.4 中不做保护(PersistentGrants.DataProtectData不生效)。在修复版本提供替换方式之前:- 对存储授权数据的数据库做好访问控制与静态加密;
- 不要假设这些载荷在数据库或浏览器端是保密的。
清理与后台任务
- 运行过期清理循环(宿主负责)。 wicket 不启动后台 goroutine。宿主定期调用各 store 的
RemoveExpired、会话 store 的DeleteExpired,示例见 持久化存储。 - 关闭时先停清理循环,再
root.Close()。
多副本
- 所有有状态 seam 指向共享存储(同一个 PostgreSQL 等)。
- 签名密钥(或密钥记录存储 + 保护器主密钥)在副本间一致。
IssuerUri在所有副本上相同。- 知晓分布式缓存的范围:注入内存版
NewMemoryDistributedCache()时,设备码轮询限速按副本独立计算;需要全局限速时自行实现services.DistributedCache。 - 清理循环在多副本上重复运行是安全的,但可只在一个副本上运行以减少数据库负载。
日志
- 宿主代码不记录令牌、授权码、刷新令牌、客户端密钥、签名私钥,只记标识符或前缀;请求日志中间件要避开
Authorization头和令牌端点的请求体。 - 在日志采集侧过滤 wicket 内部日志。 v0.1.4 中容器内部日志器在
NewRoot()时固定为 JSON 格式、Debug 级别、写到标准输出,cfg.SetLogger不会改变它,宿主目前无法调整其级别。这些日志量很大,且部分日志可能包含敏感的请求参数。请在日志采集侧按level丢弃 Debug、删除敏感字段,限制访问权限,并避免把原始日志发往不受信任的系统。详见 日志与可观测性。 - DSN 等连接串写日志前去掉口令。
浏览器与跨域
- CORS 由宿主处理。 v0.1.4 没有输出
Access-Control-*响应头的代码,CorsOptions与Client.AllowedCorsOrigins本身不产生 CORS 行为(可作为宿主中间件的判定数据)。若有单页应用从浏览器直接调用令牌、UserInfo、发现或 JWKS 端点,请在 wicket 外层自行加 CORS 中间件,并严格限定允许的来源,见 CORS 跨域。 - 设置
UserInteraction.LoginUrl与LoginReturnUrlParameter(默认均为空),并为wicket认证方案提供 cookie handler。登录页只接受本站相对的returnUrl,防止开放重定向。 - 确认
ErrorUrl(默认/home/error)、ConsentUrl(默认/consent)指向宿主真实存在的页面。
端点与功能面
- 关闭未实现端点的发现公告:
Endpoints.EnableTokenRevocationEndpoint与EnableDeviceAuthorizationEndpoint默认true,会让发现文档公布 v0.1.4 并不存在的/connect/revocation、/connect/deviceauthorization。 - 按需屏蔽不用的端点。
EnableTokenEndpoint、EnableAuthorizeEndpoint、EnableIntrospectionEndpoint、EnableBackchannelAuthenticationEndpoint只影响发现文档,不会让端点停止工作;需要屏蔽时在外层中间件拦截路径。 password授权默认拒绝;只有确实需要时才注入ResourceOwnerPasswordValidator。- 客户端密钥用
utils.Sha256哈希存放;需要轮换时同时配置新旧两个Secret,切换完成后从配置中删除旧密钥。v0.1.4 中请勿给密钥设置Expiration,见 客户端凭据。 - 公共客户端(SPA、移动 App)保持
RequirePkce = true、AllowPlainTextPkce = false。
启动与关闭
- 全部装配在启动阶段完成,
Freeze()与UseWicket()的 panic 让配置错误在部署时就失败(可配合就绪探针)。 - 服务运行中不修改
WicketOptions与ServerConfig。 - 关闭顺序:
http.Server.Shutdown→ 停止清理循环 →root.Close()→ 关闭连接池。
版本
- 固定精确版本:
github.com/gonewx/wicket v0.1.4、github.com/gonewx/wicket-pg v0.1.3(该版本依赖 wicket v0.1.4)。v0.x 期间可能有不兼容变更,升级前阅读 更新日志。 - 使用 Go 1.27 工具链构建。