场景

  • 要把员工证、学历/资质证明、活动票、会员凭证做成「发给本人、验证方离线可验、出示时只暴露必要字段」的数字凭证,签发方用 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-vcipreviewKeycloak 作为凭证签发方的主体能力
oid4vc-vci-preauth-codeexperimental预授权码流程(pre-authorized_code)
oid4vc-vci-rest-credential-offerexperimental通过 REST 端点创建 credential offer
client-auth-abcaexperimental基于 attestation 的客户端认证
oid4vc-vpexperimentalKeycloak 作为 OID4VP 验证方
oid4vc-mdocexperimentalmdoc/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 级开关

  1. Realm Settings → General:打开 Verifiable Credentials 开关。
  2. 打开后,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 Lifetime31536000(1 年)凭证库记录与 refresh token 的有效期
Credential Refresh Interval604800(7 天),或更短的凭证生命周期写入凭证 JWT 的 exp;必须不大于 Credential Lifetime
Supported Formatdc+sd-jwt可选 jwt_vc_json;mdoc 需 oid4vc-mdoc 实验特性,别在生产用
Signing Key ID / Signing Algorithm默认 realm 活跃密钥选 Key ID 会自动带出算法;SD-JWT 要求密钥满足上面的证书链条件
Cryptographic Binding Required关关掉 = 不要求持有者证明持有私钥,签发出来的是「持有者凭证」,谁拿到谁可用
Cryptographic Binding Methodsjwk(SD-JWT / JWT VC)mdoc 侧为 cose_key
Binding Supported Proof Typesjwt / attestation组合决定钱包必须提交什么 proof
Visible Claimsid,iat,nbf,exp,jtiSD-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 客户端没有区别,只多两步:

  1. Clients → 目标客户端 → Advanced:在 OpenID for Verifiable Credentials 区把 OID4VCI enabled 打开。
  2. 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 Lifetimevc.c-nonce-lifetime-seconds60(最短 30)太短会让慢网络下的钱包频繁因 nonce 过期失败
Credential Offer LifespancredentialOfferLifespanS300(最短 30)QR 码/邮件 offer 的有效窗口,过期需重开
Signed Metadata Lifespan / Algoid4vci.signed_metadata.lifespan / .alg60 / RS256仅在请求带 Accept: application/jwt 时返回签名元数据
Require Request / Response Encryptionoid4vci.request.encryption.required / oid4vci.response.encryption.required关打开后请求必须是 JWE、响应按钱包提供的密钥加密;钱包不支持就全链路失败
Batch Issuance Sizeoid4vci.batch_credential_issuance.batch_size2必须 ≥ 2,未设置或非法即关闭
Issuer Infooid4vci.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)
noncenonce 端点取回的 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 是设计行为,不是配置错误。

被攻破时的处置顺序(对应官方口径):

  1. 账号被攻破:禁用用户 → Sign out all active sessions(not-before)→ 逐个撤销 credential entitlement。前两步只挡住继续签发与刷新,钱包里已存在的凭证要靠短 refresh interval 收口。
  2. 客户端/钱包被攻破:禁用该 client(所有 token 操作含刷新立即失效),或删除并重建 client(删除会连带删除相关凭证记录)。
  3. 注意目前没有按 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_identifiercredential_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/403aud 锁定在凭证端点正常行为;调 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 的类型定义)。