☰
Apereo CAS YubiKey 多因子认证:JSON 文件注册库(JsonYubiKeyAccountRegistry)配置与实现原理
2026/10/15 19:02:48 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

Apereo CAS - Identity & Single Sign On for all earthlings and beyond.

项目地址:https://gitcode.com/gh_mirrors/ca/cas
点击查看免费下载

YubiKey 是 Apereo CAS 多因子认证(MFA)中常用的一种硬件令牌方案,而要让某位用户获准使用指定的 YubiKey 设备完成认证,CAS 需要一份"用户 → 设备"的注册记录。本文聚焦于 CAS 官方支持的其中一种注册记录存储方式——JSON 文件注册库(对应文档 YubiKey-Authentication-Registration-JSON.md):从配置文件路径指定、JSON 数据结构,到源码层面的加载、监听与写回机制,再到测试与运维端点,完整讲清"如何用一份 JSON 文件管理 YubiKey 设备注册记录"。读完本文,你将能够独立配置并理解 CAS 的 JSON 型 YubiKey 注册库,并能在多种存储后端之间做出合理选择。

一、JSON 注册库在整个 YubiKey 认证流程中的位置

在 YubiKey-Authentication.md 中可以看到,CAS 默认允许所有 YubiKey 账号参与认证,但需要通过"带外(out-of-band)注册流程"将设备记录写入某种存储后端,认证时 CAS 会在注册库中检索当前用户与设备的匹配记录,匹配成功才能放行。注册记录的存储后端有多种可选:

存储方式说明对应指南
JSON注册记录保存在一个 JSON 文件中YubiKey-Authentication-Registration-JSON.md
REST通过 REST API 读写注册记录YubiKey-Authentication-Registration-Rest.md
Permissive在配置中静态声明允许的设备YubiKey-Authentication-Registration-Permissive.md
JPA注册记录存入关系型数据库YubiKey-Authentication-Registration-JPA.md
Redis注册记录存入 RedisYubiKey-Authentication-Registration-Redis.md
DynamoDb注册记录存入 Amazon DynamoDBYubiKey-Authentication-Registration-DynamoDb.md
MongoDb注册记录存入 MongoDBYubiKey-Authentication-Registration-MongoDb.md
Custom自行实现注册库YubiKey-Authentication-Registration-Custom.md

JSON 方式的优势在于零外部依赖:不需要数据库、缓存或网络服务,只要 CAS 运行环境能读写一个文件即可,非常适合小规模部署、开发调试与快速验证场景。

二、启用 JSON 注册库:模块与配置

JSON 注册库由cas-server-support-yubikey模块提供(该模块同时也是整个 YubiKey 认证功能的基础模块),在 WAR overlay 中引入该依赖即可:

implementation "org.apereo.cas:cas-server-support-yubikey"

注册库的核心配置前缀为cas.authn.mfa.yubikey.json,对应配置模型类 YubiKeyJsonMultifactorProperties(继承自SpringResourceProperties)。常用属性如下:

配置属性类型默认值说明
cas.authn.mfa.yubikey.json.locationSpringResource无JSON 注册文件的位置。可以是file:、classpath:或普通 URL 形式;标注了@ExpressionLanguageCapable,即支持 Spring 表达式语言(如file:${user.home}/yubikey.json)。该属性一旦非空,CAS 即会选择 JSON 注册库
cas.authn.mfa.yubikey.json.watch-resourcebooleantrue是否主动监听该资源文件的变化,检测到修改后自动重新加载注册记录

此外,YubiKey 认证本身还依赖以下必填/相关属性(见 YubiKeyMultifactorAuthenticationProperties):

配置属性说明
cas.authn.mfa.yubikey.client-idYubico 云服务分配的客户端 ID(必填)
cas.authn.mfa.yubikey.secret-keyYubico 云服务分配的秘密密钥(必填)
cas.authn.mfa.yubikey.crypto注册记录的签名/加密设置(JWT 风格的加密与签名密钥),设备 publicId 在落盘前会经CipherExecutor编码
cas.authn.mfa.yubikey.multiple-device-registration-enabled是否允许同一用户注册多台设备并自动切换
cas.authn.mfa.yubikey.validator设备验证策略:VERIFY(通过 YubiKey API 校验,默认)、SKIP(跳过校验接受一切)、REJECT(拒绝一切)

一个最小可用的配置示例(application.yml):

cas: authn: mfa: yubikey: client-id: 18423 secret-key: zAIqhjui12mK8x82oe9qzBEb0As= json: location: file:/etc/cas/yubikey-registry.json watch-resource: true

说明:client-id与secret-key需要向 Yubico 申请 API 密钥;上述取值仅为项目测试代码中的示例(见下文测试章节),生产环境请使用自己的密钥。

在仓库的官方测试中也可以看到同样的配置形态(JsonYubiKeyAccountRegistryTests):

cas.authn.mfa.yubikey.client-id=18423 cas.authn.mfa.yubikey.secret-key=zAIqhjui12mK8x82oe9qzBEb0As= cas.authn.mfa.yubikey.json.location=file:${java.io.tmpdir}/yubikey.json cas.authn.mfa.yubikey.json.watch-resource=false

三、JSON 文件的数据结构

按官方文档的定义,注册文件的 JSON 结构本质上是一张"用户 ID → YubiKey 设备 public ID 列表"的映射表,概念上形如:

{ "uid1": ["yubikeyPublicId1"], "uid2": ["yubikeyPublicId2"] }

即:以用户名为键(key),以该用户名下允许使用的 YubiKey 设备 public ID 数组为值(value)。

需要说明的是,这是一个概念化的简化表示。从源码实现看,JsonYubiKeyAccountRegistry 在读取与写回时使用的ObjectMapper开启了defaultTypingEnabled(true),且账户模型 YubiKeyAccount 与设备模型 YubiKeyRegisteredDevice 上都标注了@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS),因此实际落盘的 JSON 会带有 Jackson 类型信息字段(@class),且每个用户对应一个完整的账户对象。设备对象序列化时包含的字段(对应@JsonProperty注解)为:

  • id:设备记录 ID(由System.currentTimeMillis()生成);
  • publicId:YubiKey 设备 public ID(经注册库的CipherExecutor编码后存储);
  • name:设备名称,注册时可指定;
  • tenant:多租户标识(可选);
  • registrationDate:注册时间(默认ZonedDateTime.now(ZoneOffset.UTC))。

账户对象(YubiKeyAccount)则包含username、devices(设备列表)与可选的tenant字段。理解这一差异有助于在手工编辑 JSON 文件或对接导出数据时做出正确判断。

四、源码剖析:JsonYubiKeyAccountRegistry 的加载、监听与写回

JSON 注册库的完整实现位于support/cas-server-support-yubikey-core/src/main/java/org/apereo/cas/adaptors/yubikey/registry/JsonYubiKeyAccountRegistry.java,它继承自PermissiveYubiKeyAccountRegistry(后者又继承BaseYubiKeyAccountRegistry并实现 YubiKeyAccountRegistry 接口)。其运行机制可以从四个层面理解。

1. 首次加载与文件自建

构造时通过静态方法getDevicesFromJsonResource(...)读取资源:

  • 若资源文件尚不存在,则调用jsonResource.getFile().createNewFile()主动创建空文件,并输出Created JSON resource调试日志;
  • 若文件存在且canRead()、长度大于 0,则用 Jackson 反序列化为Map<String, YubiKeyAccount>,并包装为ConcurrentHashMap;
  • 文件为空或不可读时返回空的ConcurrentHashMap。

从源码注释可以明确:之所以使用ConcurrentHashMap作为"活跃状态(live state)",是因为注册与删除操作会一边修改内存 Map、一边把整个 Map 序列化写回文件,普通 HashMap 无法同时承受"读-写-序列化"的并发场景。

2. 文件监听与自动重载

watch-resource(对应YubiKeyJsonMultifactorProperties.watchResource,默认true)控制是否启用监听:

  • 当资源是本地文件(ResourceUtils.isFile(...)为真)且watchResource为真时,构造器会创建FileWatcherService,把 JSON 文件的每次变更回调绑定到"重新加载设备数据"逻辑上,并以类名启动监听线程;
  • 实现DisposableBean接口,在容器销毁时通过destroy()关闭监听器。

这意味着:如果你在外部直接编辑 JSON 文件,开启监听后 CAS 无需重启即可感知新注册记录;反之若关闭监听(如测试环境),修改文件后需依赖查询时重新加载(见下一条)或重启生效。

3. 查询时刷新与操作后写回

  • getAccountsInternal()在返回账户集合前会先执行devices.putAll(getDevicesFromJsonResource(...)),即每次查询都会重新读一遍文件,将外部改动合并进内存状态;
  • 而save(...)、update(...)、delete(...)、deleteAll()等变更操作在执行完内存更新后,都会调用writeDevicesToFile(),用MAPPER.writer().withDefaultPrettyPrinter().writeValue(file, devices)把整个内存 Map 以美化格式完整写回文件。

也就是说,JSON 注册库的模型是"以内存 Map 为权威、以文件为持久化镜像":读时合并、写时全量落盘。理解这一点,就能解释为何该方案适合记录量不大的场景——每次变更都会全量重写文件。

4. 设备 ID 的加密存储

在 BaseYubiKeyAccountRegistry 中,registerAccountFor(...)保存设备时使用getCipherExecutor().encode(...)对从 OTP 令牌解析出的 publicId 进行编码后才写入设备记录;读取账户时又通过decodeYubikeyRegisteredDevice(...)解码。因此 JSON 文件中的publicId并非明文,而是经cas.authn.mfa.yubikey.crypto所配置密钥加密后的密文(默认使用 no-op 执行器)。这正是JsonYubiKeyAccountRegistry构造时被注入yubikeyAccountCipherExecutor的原因(见装配配置 YubiKeyAuthenticationEventExecutionPlanConfiguration)。

五、注册库的选择逻辑:何时启用 JSON

注册库的装配发生在 YubiKeyAuthenticationEventExecutionPlanConfiguration 的yubiKeyAccountRegistryBean 工厂方法中,其选择优先级如下:

  1. cas.authn.mfa.yubikey.json.location非空→ 实例化JsonYubiKeyAccountRegistry(并注入yubikeyAccountCipherExecutor),同时输出Using JSON resource [...] as the YubiKey account registry日志;
  2. 否则若cas.authn.mfa.yubikey.rest.url非空 → 使用RestfulYubiKeyAccountRegistry;
  3. 否则若cas.authn.mfa.yubikey.allowed-devices静态映射非空 → 使用PermissiveYubiKeyAccountRegistry;
  4. 否则 → 回退到OpenYubiKeyAccountRegistry(开放注册库,任何凭据都视为已注册),此时会输出警告日志,提示"所有凭据都被认为符合 YubiKey 认证资格,建议提供注册库实现"。

从源码结构可以推断,OpenYubiKeyAccountRegistry(实现见此处)的isYubiKeyRegisteredFor(...)恒返回true——若未配置任何注册库,等于不校验设备归属,安全性取决于你对 YubiKey 认证本身的信任边界。因此在生产环境配置 JSON(或任一持久化注册库)是推荐做法。

另外注意:若同时配置了json.location与rest.url,JSON 优先生效;且整个 Bean 标有@RefreshScope,配置变更可动态刷新。

六、运维与验证:Actuator 端点与测试用例

1. 管理端点yubikeyAccountRepository

YubiKey 模块注册了 Actuator 端点yubikeyAccountRepository(实现见 YubiKeyAccountRegistryEndpoint,默认访问级别为Access.NONE,需显式开放),可用于在线查看与维护注册记录:

方法路径作用
GET/actuator/yubikeyAccountRepository返回全部 YubiKey 账户
GET/actuator/yubikeyAccountRepository/{username}查询指定用户的账户(不存在时返回空)
GET/actuator/yubikeyAccountRepository/export将所有账户打包为 zip 文件下载
POST/actuator/yubikeyAccountRepository/import以 JSON 提交YubiKeyDeviceRegistrationRequest,在线注册账户
DELETE/actuator/yubikeyAccountRepository/{username}删除指定用户及其设备记录
DELETE/actuator/yubikeyAccountRepository删除全部记录

这些操作最终都会落到JsonYubiKeyAccountRegistry的save/delete之上,进而触发writeDevicesToFile()全量写回,因此通过端点做的任何改动会实时反映到 JSON 文件中。

2. 测试用例如何验证 JSON 注册库

仓库在support/cas-server-support-yubikey/src/test/java/org/apereo/cas/adaptors/yubikey/registry/JsonYubiKeyAccountRegistryTests.java中通过 Spring Boot 上下文加载配置(json.location=file:${java.io.tmpdir}/yubikey.json、watch-resource=false),并复用抽象测试基类 AbstractYubiKeyAccountRegistryTests 覆盖以下行为:

  • 注册判定:未注册用户返回false;非法令牌(123456)无法注册;
  • 多设备注册:同一用户可注册多台设备(测试中连续注册两个 OTP 令牌后getDevices().size()为 2),并可逐个删除设备;
  • 加密一致性:verifyCipher()验证 publicId 经CipherExecutor编码/解码后保持一致;
  • 账户保存与删除:save后能查到账户,delete后账户为空。

这些用例从侧面印证了 JSON 注册库"读文件建 Map、写操作落盘"的实现契约,你也可以参考它们在自己的部署中快速自测。

七、相关文档导航

  • YubiKey 认证总览(含设备验证、REST 协议凭据提取yubikeyotp):YubiKey-Authentication.md
  • 其他注册存储后端:REST、Permissive、JPA、Redis、DynamoDb、MongoDb、自定义
  • MFA 触发、绕过与失败模式配置:Configuring-Multifactor-Authentication.md、Configuring-Multifactor-Authentication-Bypass.md、Configuring-Multifactor-Authentication-FailureModes.md

小结:JSON 注册库是 CAS YubiKey MFA 中最轻量的注册记录存储方案——只需一行json.location配置即可启用,源码层面通过"内存 Map + 文件监听 + 全量写回"实现持久化与热更新。理解其数据结构(含类型信息与加密字段)、选择优先级与 Actuator 运维端点,即可在中小规模场景下快速落地 YubiKey 设备注册与认证管理。

  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

Apereo CAS - Identity & Single Sign On for all earthlings and beyond.

项目地址:https://gitcode.com/gh_mirrors/ca/cas
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询