核心概念
本页把 wicket 里反复出现的名词串成一张图:谁签发令牌、给谁签、签什么、凭什么签,以及这些东西在代码里由谁装配。
签发者(issuer)
签发者是授权服务器的身份标识,写进每个令牌的 iss 声明,也是发现文档里的 issuer 字段。
- 设置了
WicketOptions.IssuerUri时,签发者就是它(去掉末尾/)。 - 未设置时,wicket 按当前请求推导:
http(s)://+r.Host,其中是否https只看r.TLS是否非空。
生产环境务必显式设置 IssuerUri,否则同一个服务经不同主机名访问会得到不同的签发者,令牌校验随之失败。
opts := godi.GetRequired[*options.WicketOptions](root)
opts.IssuerUri = "https://id.example.com"客户端(Client)
客户端是向 wicket 申请令牌的应用:后端服务、单页应用、移动 App、设备等。由 storage/models.Client 描述,关键字段:
ClientId/ClientSecrets:客户端标识与密钥(密钥以utils.Sha256哈希后存放)。- 允许的授权类型:通过
SetAllowedGrantTypes设置(字段本身未导出,会校验非法组合)。 AllowedScopes:客户端可以申请的作用域。RedirectUris:授权码流回跳地址白名单。
详见 客户端与资源。
资源与作用域
wicket 把“令牌能访问什么”拆成三类模型:
| 模型 | 作用 | 出现在 |
|---|---|---|
IdentityResource(身份资源) | 一组用户身份声明,如 openid→sub、profile→name 等 | 身份令牌、UserInfo |
ApiScope(API 作用域) | 客户端可申请的一项访问权限,如 orders.read | 访问令牌的 scope |
ApiResource(API 资源) | 一个受保护的 API,把若干 ApiScope 归组,并持有内省用的 ApiSecrets | 访问令牌的 aud;内省端点的调用方身份 |
客户端请求 scope=openid profile orders.read 时,openid、profile 匹配身份资源,orders.read 匹配 API 作用域,其所属的 API 资源名进入访问令牌受众。
令牌
| 令牌 | 用途 | 形态 |
|---|---|---|
| 访问令牌(access token) | 调用 API | JWT(头部 typ 为 at+jwt),默认 RS256 签名 |
| 引用令牌(reference token) | 访问令牌的另一种形态 | 不透明句柄,令牌内容存在 ReferenceTokenStore,API 需调用内省端点校验。v0.1.4 尚不签发,见下方说明 |
| 身份令牌(id token) | 向客户端证明“谁登录了” | JWT,仅在 OIDC 请求(含 openid 作用域)中签发 |
| 刷新令牌(refresh token) | 换取新的访问令牌 | 不透明句柄,存于 RefreshTokenStore;客户端需 AllowOfflineAccess = true 并申请 offline_access |
引用令牌在 v0.1.4 中尚未接通
Client.AccessTokenType 字段可以设为 wicket.AccessTokenTypeReference,但 v0.1.4 的签发路径没有读取它:实测设置后令牌端点仍返回 JWT。ReferenceTokenStore 目前只在令牌校验一侧使用。需要“可即时作废”的访问令牌时,请缩短 AccessTokenLifetime。
授权类型(grant type)
| 授权类型 | 常量 | 典型场景 |
|---|---|---|
client_credentials | wicket.GrantTypesClientCredentials | 服务对服务,无用户参与 |
authorization_code | wicket.GrantTypesCode | 有用户登录的 Web / 移动 / SPA,默认要求 PKCE |
refresh_token | — | 用刷新令牌续期。无需列入客户端允许的授权类型;刷新令牌只在客户端 AllowOfflineAccess = true 且请求 offline_access 时签发 |
CIBA(urn:openid:params:grant-type:ciba) | wicket.GrantTypesBackchannelAuthentication | 后端通道发起、用户在另一设备确认 |
设备码(urn:ietf:params:oauth:grant-type:device_code) | wicket.GrantTypesDeviceFlow | 令牌端点可处理,但 v0.1.4 没有内置设备授权端点 |
password | wicket.GrantTypesPassword | 默认拒绝;需宿主用 cfg.SetResourceOwnerValidator 注入校验器 |
各流程的完整步骤见 授权流程。
认证方案
“认证方案”是宿主应用识别浏览器端已登录用户的方式。wicket 预置两个基于 cookie 的方案名:
| 方案名 | 常量 | 用途 |
|---|---|---|
wicket | wicket.DefaultCookieAuthenticationScheme | 用户在宿主登录页完成登录后的主会话 cookie |
wicket.external | wicket.ExternalCookieAuthenticationScheme | 外部登录(Google、Facebook 等)回调期间暂存外部身份 |
v0.1.4 没有内置 cookie 处理器
这两个方案默认注册的是一个“不支持”的占位 handler:认证永远得到“未登录”,Challenge 直接报错。要跑通需要用户登录的流程(授权码、结束会话等),宿主必须为这两个方案提供自己的 auth.Handler 实现:
cfg.SetAuthenticationHandlerProvider(wicket.DefaultCookieAuthenticationScheme,
func(p godi.Provider) auth.Handler { return newMyCookieHandler(p) }) // newMyCookieHandler 由宿主实现(示意)client_credentials 等不涉及浏览器用户的流程不受影响。
此外还内置了 Google、Facebook、Bearer(JWT Bearer)几个方案的 handler,见 身份接入。
组合根(di.Root)与 ServerConfig
wicket 的全部服务由一个代码生成的依赖注入容器装配,入口是 di.NewRoot():
root, err := di.NewRoot() // 返回 (*di.Root, error)
if err != nil { log.Fatal(err) }
defer root.Close()
opts := godi.GetRequired[*options.WicketOptions](root) // 协议选项
cfg := root.ServerConfig() // 宿主注入点两个对象分工明确:
*options.WicketOptions:协议行为的开关与参数(签发者、端点、交互页 URL、输入长度限制……)。见 配置选项。*di.ServerConfig:宿主向 wicket 注入“外部世界”的地方——客户端与资源列表、签名凭据、各类存储、认证 handler、用户资料服务等。所有注入方法都是SetXxx(value)或SetXxxProvider(func() value)两种形式。
装配完成后调用 cfg.Freeze():它校验所有存储端口都已注入(或已声明内存回退),否则立即 panic。之后用 server.DefaultServer(root, ...) 构造服务器、用 srv.UseWicket() 得到中间件。详见 嵌入宿主应用。
存储端口三族
wicket 的有状态数据通过三族接口(端口)访问,宿主注入实现:
| 端口族 | 包 | 存什么 | 注入方法 |
|---|---|---|---|
storage | storage | 授权码、刷新令牌、引用令牌、用户同意、持久化授权、设备码、CIBA 请求、授权参数消息 | SetAuthorizationCodeStore、SetRefreshTokenStore 等 |
session | session | 服务端会话记录 | SetSessionStore |
keymgmt | keymgmt | 自动管理的签名密钥及其生命周期 | SetRecordStore |
另有两类不属于“持久化”的输入:客户端与资源(SetClients 等,配置数据)、签名凭据与验签密钥(SetSigningCredentialStores / SetValidationKeysStores)。
每族都有对应的契约测试套件(storage/storagetest、session/sessiontest、keymgmt/keymgmttest),适配器通过套件后才算合规。详见 持久化存储 与 存储端口。