IAM 数字凭证:Keycloak OID4VCI 签发与撤销边界 | IDaaS Book
场景
- 要把员工证、学历/资质证明、活动票、会员凭证做成「发给本人、验证方离线可验、出示时只暴露必要字段」的数字凭证,签发方用 Keycloak。
- 已经按 OID4VCI 的方向建了 realm 和 client,但 Issuer Metadata 里
credential_configurations_supported是空的,或者 SD-JWT VC 一签发就失败。 - 更前置的问题是决策:可验证凭证能不能用来做内部访问控制。这一条的答案决定了这套东西该不该上,见第 2 节和第 6 节。
这篇只讲 Keycloak 作为签发方(Issuer)这一条链路:能签什么、怎么配、怎么验证、撤销的边界在哪里。钱包与验证方(Verifier)只在与签发相关的边界处提及。
1. 先摆正信任模型:VC 不是「多了一种 Token」
OpenID for Verifiable Credential Issuance(OID4VCI)1.0 已是 OpenID Foundation 的正式规范(Final,2025-09-16 发布)。它在 OAuth 2.0 之上定义了一套受保护的凭证签发 API,信任模型是三角色的:
graph LR
I[Issuer 签发方<br/>Keycloak] -->|签发凭证| H[Holder 持有者<br/>钱包]
H -->|出示凭证| V[Verifier 验证方<br/>业务系统]
V -.->|验证签名,不回连签发方| I
- Issuer:签名并签发凭证,Keycloak 实现的是这一角,规范是 OID4VCI。
- Holder:持有并存储凭证的个人或其钱包。
- Verifier:验证凭证真实性与完整性的业务方,规范是 OID4VP。
规范明确的一条隐私性质:签发方与验证方不直接通信,持有者决定出示哪些字段给谁。所以签发方无法知道凭证在哪里、何时、如何被使用——这不是实现省略,而是设计目标,也直接导致第 6 节的撤销问题。
与 OIDC 的关系容易被高估或低估:
- 钱包本身就是一个标准的 OAuth 2.0 客户端,走的是 Keycloak 现成的授权端点、登录页、认证流(授权码 + PKCE 都复用)。凭证不在 ID Token 或 Token 响应里,而是拿 access token 到单独的凭证端点换。
- Keycloak 26.8.0 也提供了 OID4VP 验证方能力,但属实验特性,本文不展开。
把凭证当成 Token 的变体,后面每一处都会判断错。三处根本差异:
| 维度 | OAuth/OIDC Token | 可验证凭证(VC) |
|---|---|---|
| 验证方式 | 资源服务器本地校验,或回源 introspection | 密码学离线验证,验证方不需要联系签发方 |
| 载体与归属 | 留在你的系统边界内,可随时吊销 | 存进用户钱包,已离开你的控制域 |
| 撤销 | 会话结束 / not-before / introspection 立即可见 | 撤销只阻止后续刷新,已在钱包里的凭证在 exp 前仍然密码学有效 |
| 受众 | aud 锁定资源服务,服务于 API 授权 | 面向验证方出示,服务于「证明某个事实」 |
2. 适用与不适用
适用(凭证的验证方不在你的域内,或需要选择性披露)
| 场景 | 为什么适合 |
|---|---|
| 学历、资质、从业资格 | 验证方(雇主、监管)不需要回连学校或发证机构 |
| 员工证/访客证的对外出示 | 只出示「在职」而不暴露工号、部门、邮箱 |
| 活动票、会员凭证 | 离线可验证,会场无网络也能验 |
| 跨组织互认(B2B、行业联盟) | 各方只共享信任根,不共享用户库 |
不适用(别为了用新技术把现有体系换掉)
| 需求 | 更合适的做法 |
|---|---|
| 内部 API 授权、微服务鉴权 | OAuth 2.0 Token + 资源服务器验签,见 IAM 会话管理与 Token 生命周期 |
| 解雇/降权必须当天立即失效 | 服务端会话 + not-before,或让 verifier 在线校验并自建撤销名单;VC 的撤销模型做不到 |
| 需要服务端集中管控会话与设备 | IdP 侧会话管理、SCIM 下线流程 |
| 机器身份(CI、Pod、服务间调用) | SPIFFE/SPIRE 工作负载身份,与人类 IAM 分工不同,见 SPIFFE/SPIRE 工作负载身份 |
判断标准只有一句:如果你的安全需求里包含「撤销必须即时生效」,先别用 VC 承载它,或者必须接受「让验证方在线查撤销状态」这个额外的工程成本。
3. Keycloak 侧能力与成熟度(26.8.0)
Keycloak 26.8.0 把 OID4VCI 从 experimental 提升为 preview,相关能力被拆成互相独立的特性开关:
| 特性开关 | 26.8.0 状态 | 说明 |
|---|---|---|
oid4vc-vci | preview | Keycloak 作为凭证签发方的主体能力 |
oid4vc-vci-preauth-code | experimental | 预授权码流程(pre-authorized_code) |
oid4vc-vci-rest-credential-offer | experimental | 通过 REST 端点创建 credential offer |
client-auth-abca | experimental | 基于 attestation 的客户端认证 |
oid4vc-vp | experimental | Keycloak 作为 OID4VP 验证方 |
oid4vc-mdoc | experimental | mdoc/mDL 格式(26.8.0 新增) |
开启方式二选一:
# 最小集合:只开凭证签发
kc.sh start --features=oid4vc-vci
# 或者把整个 preview 集合一起打开
kc.sh start --features=preview不要在生产上用 --features=preview。 该章开头的 WARNING 写得很直白:This feature is in preview and is not fully supported … Backward compatibility is not guaranteed, and future updates may introduce breaking changes.,特性指南对 preview 级别的定义里还有一句更直接的 not recommended for use in production。而且一把打开会连带启用其他 preview 能力(例如 Client Admin API v2、参数化 scope),把升级面从「一个特性」放大成「一批特性」。生产用最小集合 --features=oid4vc-vci,需要哪个实验特性就单独加哪个。
验证是否真的生效:启动日志会打印一行 Preview features enabled: oid4vc-vci(org.keycloak.common.Profile#logUnsupportedFeatures 对 preview 走 INFO 级、experimental 走 WARN 级),比重启后翻控制台更确定;确认这行之后再检查 realm 开关(下一节)。顺手对比一下
IAM 升级与零停机滚动更新 里 kc.sh update-compatibility 的用法——特性开关属于启动期配置,跨版本升级前要先把特性集合固定下来。
4. 最小配置
4.1 realm 级开关
- Realm Settings → General:打开 Verifiable Credentials 开关。
- 打开后,General 页的 Endpoints 区会出现 OID4VC Issuer Metadata 链接;Tokens 页会多出 OID4VCI Attributes 区。
这两处都是 realm 级配置,不需要重启。
4.2 签名密钥:第一次上手最容易卡住的地方
凭证用 realm 的签名密钥签发。SD-JWT VC 对密钥有硬要求(对齐 HAIP 的 issuer key resolution 要求):
- 叶证书必须是凭证签名密钥的证书,且不能自签;
- 中间证书按链顺序跟在叶证书之后;
- trust anchor 不能出现在
x5c里:链尾若是自签根,Keycloak 会在生成 SD-JWT VC 时自动去掉;但如果是非自签的 trust anchor,Keycloak 不会替你删,必须在配置时就从链里去掉。
官方文档明确:不要用生成的 realm key 签 SD-JWT VC。生成的密钥要么没有证书,要么用自签证书,SD-JWT VC 签发会直接失败(
CredentialSignerException)。JWT VC 不受这条限制。
推荐的配置路径:P-256 + ES256,把私钥与非自签叶证书链放进 Java keystore,然后在Realm Settings → Keys → Providers 添加 java-keystore provider(keystore 路径/类型/口令/alias),算法选 ES256、Key use 选 sig,把 provider 置为 active 并给它足够优先级成为 realm 的活跃 ES256 密钥;也可以不改优先级,而是在凭证 client scope 里显式指定 Signing Key ID。
如果这套密钥还要承担 mTLS 或 X.509 登录,注意金库与信任库是两回事、别复用同一份证书链,边界见 Keycloak X.509 客户端证书登录。
4.3 client scope = 一种凭证类型
Keycloak 用 ClientScope 表达「一种可签发的凭证」,协议必须选 OpenID for Verifiable Credentials。两个开关决定两件完全不同的事,配错一个就会出现「登录成功了但钱包发现不了凭证」这类现象:
| 开关 | 作用 | 不开的后果 |
|---|---|---|
| Include in token scope | 请求的凭证 scope 出现在 access token 的 scope 声明里 | 凭证端点拒绝签发 |
| Include in OpenID Provider Metadata | 该凭证配置出现在 Issuer Metadata 的 credential_configurations_supported 里 | 钱包在元数据里根本看不到这类凭证 |
scope 本身的关键字段(Admin Console:Client Scopes → Create client scope):
| 字段 | 默认值 | 说明 |
|---|---|---|
| Credential Configuration ID | 取 scope 名 | 元数据 credential_configurations_supported 的键 |
| Credential Identifier | 取 scope 名 | 签发过程中使用的标识,凭证请求必须与 token 响应里的一致 |
| Credential Offer Required | 关 | 打开后必须先有 credential offer 才能签发(预授权码流程用) |
| Credential Lifetime | 31536000(1 年) | 凭证库记录与 refresh token 的有效期 |
| Credential Refresh Interval | 604800(7 天),或更短的凭证生命周期 | 写入凭证 JWT 的 exp;必须不大于 Credential Lifetime |
| Supported Format | dc+sd-jwt | 可选 jwt_vc_json;mdoc 需 oid4vc-mdoc 实验特性,别在生产用 |
| Signing Key ID / Signing Algorithm | 默认 realm 活跃密钥 | 选 Key ID 会自动带出算法;SD-JWT 要求密钥满足上面的证书链条件 |
| Cryptographic Binding Required | 关 | 关掉 = 不要求持有者证明持有私钥,签发出来的是「持有者凭证」,谁拿到谁可用 |
| Cryptographic Binding Methods | jwk(SD-JWT / JWT VC) | mdoc 侧为 cose_key |
| Binding Supported Proof Types | jwt / attestation | 组合决定钱包必须提交什么 proof |
| Visible Claims | id,iat,nbf,exp,jti | SD-JWT 格式下始终明示的声明 |
Claims 由 protocol mapper 决定,最小可用组合是三个 User Attribute mapper(given_name → firstName、family_name → lastName、email → email)加一个 Issued At Time Claim Mapper(iat,可截断到 HOURS)。mapper 的 vc.display 提供钱包展示用的名称。
一个容易误解的点:Keycloak 在给用户建立凭证 entitlement 时会存一份属性快照,但这份快照不冻结 claim——签发和刷新时读的是用户当前属性。改用户属性不会自动重签已经在钱包里的凭证,要等钱包刷新或重新签发。
4.4 client 侧
凭证请求的客户端和普通 OIDC 客户端没有区别,只多两步:
- Clients → 目标客户端 → Advanced:在 OpenID for Verifiable Credentials 区把 OID4VCI enabled 打开。
- Client scopes 页把凭证 scope 加为 Optional(推荐;不要让所有登录都带上它)。
用 REST 建客户端时对应两个字段:attributes["oid4vci.enabled"] = "true",以及 optionalClientScopes 里带上凭证 scope 名。
4.5 realm 级 OID4VCI 参数
Admin Console 在 Realm Settings → Tokens → OID4VCI Attributes:
| 设置 | realm 属性 | 默认 | 生产注意 |
|---|---|---|---|
| OID4VCI Nonce Lifetime | vc.c-nonce-lifetime-seconds | 60(最短 30) | 太短会让慢网络下的钱包频繁因 nonce 过期失败 |
| Credential Offer Lifespan | credentialOfferLifespanS | 300(最短 30) | QR 码/邮件 offer 的有效窗口,过期需重开 |
| Signed Metadata Lifespan / Alg | oid4vci.signed_metadata.lifespan / .alg | 60 / RS256 | 仅在请求带 Accept: application/jwt 时返回签名元数据 |
| Require Request / Response Encryption | oid4vci.request.encryption.required / oid4vci.response.encryption.required | 关 | 打开后请求必须是 JWE、响应按钱包提供的密钥加密;钱包不支持就全链路失败 |
| Batch Issuance Size | oid4vci.batch_credential_issuance.batch_size | 2 | 必须 ≥ 2,未设置或非法即关闭 |
| Issuer Info | oid4vci.issuer_info | 未设 | EUDI 场景发布 registration_cert 等 issuer_info |
另外有一组隐私配置(Time Claim Correlation Mitigation):oid4vci.time.claims.strategy 取 off / randomize(在 oid4vci.time.randomize.window.seconds 窗口内随机减一个偏移,默认 86400 秒)/ round(按 oid4vci.time.round.unit 截断到 SECOND/MINUTE/HOUR/DAY)。作用是不让凭证里的精确时间戳成为跨签发、跨出示的关联抓手——如果这类凭证会对同一个验证方反复出示,值得打开。
5. 签发:两条流程与最小验证命令
规范定义两条签发路径,Keycloak 都实现,区别只在「用户是否需要交互认证」:
sequenceDiagram
participant W as 钱包(OAuth Client)
participant U as 用户
participant KC as Keycloak
participant CE as 凭证端点 /protocol/oid4vc/credential
W->>KC: 授权请求(scope 或 authorization_details)
KC->>U: 登录 + 同意
U-->>KC: 认证通过
KC-->>W: authorization code
W->>KC: token 端点换 access token
KC-->>W: access token + credential_identifiers
W->>KC: POST nonce 端点取 c_nonce
KC-->>W: c_nonce
W->>CE: credential request(credential_identifier + JWT proof)
CE-->>W: 签名凭证(+ 新 c_nonce)
- 授权码流程:用户交互认证、明确同意后签发。适合「每张凭证都应经本人确认」的场景。
- 预授权码流程:签发方先创建 credential offer(Admin Console、Admin REST API,或端点
/protocol/oid4vc/create-credential-offer,后者属实验特性oid4vc-vci-rest-credential-offer),把 pre-authorized code 通过二维码/链接/邮件交给钱包,钱包不需要实时认证即可领取。适合批量签发、现场发证、用户已通过其他方式认证过的场景。可选的tx_code是第二层校验,但必须与 offer 本身走不同通道,否则等于没有第二因子。
从零验证一条链路,命令序列是固定的(把 {realm} 换成你的 realm):
# 1) 元数据:确认凭证类型已被发布,拿到各端点
curl -s "https://kc.example.com/.well-known/openid-credential-issuer/realms/{realm}" | jq '{
endpoint: .credential_endpoint,
batch: .batch_credential_endpoint,
nonce: .nonce_endpoint,
configs: (.credential_configurations_supported | keys)
}'
# 2) 取 nonce(需带 access token)
curl -s -X POST "https://kc.example.com/realms/{realm}/protocol/oid4vc/nonce" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# 3) 凭证请求:credential_identifier 必须用 token 响应里的原值
curl -s -X POST "https://kc.example.com/realms/{realm}/protocol/oid4vc/credential" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"credential_identifier": "membership-credential",
"proofs": { "jwt": ["<JWT_PROOF>"] }
}'请求凭证时优先用 authorization_details 而不是把 scope 塞进 URL:
[{ "type": "openid_credential", "credential_configuration_id": "membership-credential" }]官方推荐再用 PAR 端点(/protocol/openid-connect/ext/par/request)把这个 JSON 从浏览器 URL 里挪走——authorization_details 很容易超长,而且参数出现在 URL 里本身就不该被接受,边界见
Keycloak PAR 实战。
JWT proof 是钱包证明「我持有这把私钥」的唯一凭据,字段要求很硬:
| proof 部分 | 要求 |
|---|---|
header typ | 必须是 openid4vci-proof+jwt |
header alg | 必须是元数据里 proof_signing_alg_values_supported 列出的算法(如 ES256/RS256/PS256) |
| header 绑定密钥 | jwk、kid、x5c 三选一,只能给一个 |
aud | 必须是签发方标识(issuer identifier) |
nonce | nonce 端点取回的 c_nonce |
iat | 必须与服务端时间相差 30 秒以内 |
iss | 客户端绑定流程下等于 client_id;匿名流程则不带该声明 |
签发结果里的持有者绑定:JWT VC 把绑定密钥写进凭证的 cnf 声明;SD-JWT 格式下绑定密钥随 disclosure 参与 _sd_hash(官方文档口径)。验证方在出示环节要先取 cnf / 校验 _sd_hash,再让持有者用私钥签一次挑战,才算完成绑定校验。如果 scope 里 Cryptographic Binding Required 是关的,这两个字段都不会有——签出来的是持有者凭证,复制即得,任何对外场景都应打开绑定。
6. 撤销:这里和 Token 完全不同
这是整套机制里最容易被误判的一段,也是把 VC 用在错误场景的主要原因。
撤销是两级的:
- 撤销单张已签发凭证:删除该条记录,钱包不能再刷新这张凭证。
- 撤销凭证类型 entitlement:删除该用户该类型的全部已签发记录,并阻止后续签发。
但 Keycloak 目前没有状态列表(status list)机制供验证方查询。 官方文档写得很直白:服务端撤销只删库记录、阻止后续刷新,钱包里已经持有的凭证在其 exp 之前仍然密码学有效;要限制暴露窗口,手段是把 Credential Refresh Interval(vc.refresh_interval_in_seconds)配短,例如 24 小时——24 小时后钱包无法刷新,凭证自然失效。也就是说「撤销生效时间」≈「refresh interval」,不是毫秒级。
三条相关的生命周期事实:
- OID4VCI 的 refresh token 与用户会话解耦。登出、结束单个会话都不会删除凭证记录;这个 refresh token 的有效期由 Credential Lifetime 决定(默认 1 年)。官方单独用 WARNING 提示:它必须被钱包安全存储。
- 设 not-before(Sign out all active sessions)只阻止刷新,已签发的记录仍在库里变成 stale,不会被删除。
- OID4VCI 的 access token 是用途锁定的:
aud指向凭证端点 URL,因此无法调用 introspection、Admin REST、Account REST。拿它去调别的 API 报 401/403 是设计行为,不是配置错误。
被攻破时的处置顺序(对应官方口径):
- 账号被攻破:禁用用户 → Sign out all active sessions(not-before)→ 逐个撤销 credential entitlement。前两步只挡住继续签发与刷新,钱包里已存在的凭证要靠短 refresh interval 收口。
- 客户端/钱包被攻破:禁用该 client(所有 token 操作含刷新立即失效),或删除并重建 client(删除会连带删除相关凭证记录)。
- 注意目前没有按 client 批量撤销的 API;已签发记录里带有 Wallet Client 信息,用于定位受影响范围。
一句话总结边界:凭证的撤销语义是「过期 + 停止续期」,不是「立即作废」。需要「立即作废」的场景,要么让验证方在线校验并自建撤销名单,要么不要用凭证承载。
7. 常见错误对照表
| 症状 | 根因 | 处理 |
|---|---|---|
SD-JWT VC 签发失败,日志 CredentialSignerException | 签名密钥没有证书链,或叶证书自签(含直接使用生成的 realm key) | 换成 java-keystore 提供的 P-256/ES256 非自签叶证书链,或在 scope 里显式指定 Signing Key ID |
元数据里 credential_configurations_supported 为空 | scope 的 Include in OpenID Provider Metadata 没开,或该 scope 没分配给 client | 打开开关;Client → Client scopes 里加为 Optional |
凭证端点报 unknown_credential_identifier | credential_identifier 与 token 响应 authorization_details.credential_identifiers 不一致 | 用 token 响应里的原值,不要自己拼 scope 名 |
| proof 校验失败 | nonce 过期(默认 60 秒,最短 30)、iat 与服务端偏差超过 30 秒、typ 写错、jwk/kid/x5c 给了多个 | 重新取 nonce;校时(NTP);按第 5 节的字段表逐项核对 |
| 钱包提示 offer 失效 | credentialOfferLifespanS 默认 300 秒已过 | 重开 offer,或按业务调整该属性(≥30 秒) |
| 用取凭证的 access token 调其他 API 401/403 | aud 锁定在凭证端点 | 正常行为;调 Admin/Account API 用相应客户端自己的令牌 |
| 改了用户属性,凭证里还是旧值 | claim 只在签发/刷新时读取当前属性,已签发的凭证不会自动更新 | 等钱包按 refresh interval 刷新,或撤销后重新签发 |
| 钱包在元数据里看不到凭证类型 | scope 没挂到 client,或 oid4vci.enabled 没开 | 按 4.4 节检查两个开关 |
8. 回滚与降级
按「影响面从小到大」的顺序回退,每一步都可独立验证:
# 1) 停发:把凭证 scope 从 client 的 optional scopes 摘掉(新令牌不再带该 scope)
# 或关闭 client 的 OID4VCI:Clients → Advanced → OID4VCI enabled = Off
# 2) 关 realm 级开关:Realm Settings → General → Verifiable Credentials = Off
# 3) 移除启动参数:去掉 --features=oid4vc-vci 后滚动重启必须同时接受两个事实:
- 回滚不会收回已签发的凭证。 它们仍在
exp之前有效,撤销动作要按第 6 节单独执行。 - 关 realm 开关后,该 realm 不再对外提供凭证签发相关端点与元数据配置(这个开关的作用域就是 realm 级 OID4VCI 功能),已经配好钱包的用户会看到凭证类型消失、随后刷新失败——这是预期的降级行为,但要提前通知,别当成事故排查。
安全事件下的正确顺序是先撤销、后回滚:禁用用户与 client → 结束会话(not-before)→ 撤销 entitlement → 再关特性。
9. 钱包侧联调顺手记
- 官方提供了 Lissi 与 Valera 两个钱包的联调指南(含暴露本地 Keycloak 给钱包访问的步骤),以及
keycloak-oauth-sig/oid4vci-deployment与 FAPI playground 两个示例(官方明确示例不代表受支持)。 - 最省事的发放入口:Admin Console 里给用户发 Send credential offer(需要 realm 的 SMTP 已配好,见 Keycloak SMTP 邮件配置与密码重置),或让用户在 Account Console 的 Verifiable credentials 页自助 Issue to wallet。
- 想在登录过程中直接弹二维码,用内置的 AIA
verifiable_credential_offer(通过kc_action=verifiable_credential_offer:<base64url-json>触发,JSON 里的credentialConfigurationId必填,preAuthorized、clientId可选)。默认要求用户 5 分钟内完成过认证,可在 required action 里把 Max Age 设为0强制每次重认证。
常见问题(IAM FAQ)
Keycloak 的 OID4VCI 现在能上生产吗? 26.8.0 里它是 preview,官方对该级别的表述是 This feature is in preview and is not fully supported,并明确 Backward compatibility is not guaranteed, and future updates may introduce breaking changes;特性指南里还有一句更直接的定义:preview 特性默认关闭,且 not recommended for use in production。适合做 POC、联调与内部场景试点;要进生产,先把特性集合固定在最小集合并把升级验证流程补齐,参考 IAM 升级与零停机滚动更新 里的做法。
撤销一张凭证要多久生效?
取决于 Credential Refresh Interval,而不是撤销动作本身。撤销会立即删掉凭证记录、阻止刷新,但钱包里那张凭证在 exp 前仍然密码学有效;Keycloak 当前没有状态列表供验证方查询,所以生效时间 ≈ 刷新间隔。对撤销时效有硬要求的场景,把刷新间隔压到 24 小时或更短,或者让验证方在线校验。
可验证凭证能替代 OAuth Token 做 API 授权吗?
不应该。OAuth Token 的 aud、scope、introspection 和撤销链路是为 API 授权设计的;VC 的设计目标是「向验证方证明某个事实」,签发方与验证方不通信。把 VC 塞进 API 网关当 Token 用,会同时失去即时撤销和服务端会话管控。API 授权继续用
IAM 会话管理与 Token 生命周期 里的那套机制。
SD-JWT VC 和 JWT VC 怎么选?
需要选择性披露就选 SD-JWT VC(dc+sd-jwt,也是 client scope 的默认格式,可用 Visible Claims 控制始终明示的声明);只做整体出示、且不想处理证书链强制要求的场景,jwt_vc_json 更省事——生成的 realm key 在 JWT VC 上是可用的,SD-JWT VC 上不行,这条差异经常是选型的决定因素。两个格式都支持持有者绑定(jwk),绑定校验语义一致。mdoc 在 26.8.0 是实验特性,不要用。
为什么我的元数据里没有 credential_configurations_supported?
两个开关分别控制两件事:Include in token scope 管 access token 里有没有 scope,Include in OpenID Provider Metadata 管元数据里有没有凭证配置。只开了前者,结果是「能授权、但钱包发现不了」。
参考来源
- OpenID for Verifiable Credential Issuance 1.0(Final,2025-09-16):状态与发布时间、Issuer/Holder/Verifier 三角色、OAuth 2.0 之上定义受保护的凭证签发 API、签发方与验证方不直接通信。
- Keycloak 26.8.0 Release Notes:OID4VCI 由 experimental 提升为 preview、
--features=oid4vc-vci与--features=preview的关系、oid4vc-vci-preauth-code/oid4vc-vci-rest-credential-offer/client-auth-abca/oid4vc-vp仍为实验特性、oid4vc-mdoc新增;stateless(multi-cluster v2)转正与multi-site弃用。 - Keycloak Server Administration Guide — Configuring Keycloak as a Verifiable Credential Issuer:preview 警示与启用方式、realm 级 Verifiable Credentials 开关与 OID4VC Issuer Metadata 端点、密钥管理与 HAIP 证书链要求(禁止用生成密钥签 SD-JWT VC)、凭证 client scope 全部字段与默认值、OID4VCI realm 属性与时间声明归一化、protocol mapper 最小配置、credential offer AIA 与
kc_action参数。 - 同章后续小节:Issuer Metadata 与凭证请求流程(
authorization_details、credential_identifiers、unknown_credential_identifier)、JWT proof 的 header/claims 要求与绑定密钥cnf、 撤销模型与生命周期(无状态列表、refresh token 与会话解耦、access token 用途锁定、按 client 批量撤销缺失)、Lissi / Valera 钱包联调指南。 - Keycloak OID4VCI 示例部署与 FAPI playground(官方标注为示例,不代表受支持)。
- Enabling and disabling features — Keycloak:
oid4vc-vci属 preview 特性、--features=preview的语义,以及 preview 级别定义(默认关闭、不建议用于生产、后续版本可能变更或移除)。 - Keycloak 26.8.0 源码:
docs/documentation/server_admin/topics/oid4vci/vc-issuer-configuration.adoc(正文所有配置项与默认值)、common/src/main/java/org/keycloak/common/Profile.java(Preview features enabled:日志级别与 preview/experimental 的类型定义)。