news 2026/9/26 2:03:47

Apereo CAS 自定义 Passwordless 认证用户账户存储(PasswordlessUserAccountStore)完整实现指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apereo CAS 自定义 Passwordless 认证用户账户存储(PasswordlessUserAccountStore)完整实现指南
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

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

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

导读

本文聚焦 Apereo CAS 无密码(Passwordless)认证体系中"用户账户存储"这一核心扩展点,讲解如何通过实现PasswordlessUserAccountStore接口、注册自定义 Spring Bean 来替换或扩展现有账户数据源。阅读本文后,你将掌握:PasswordlessUserAccountStore接口的定义与职责、CAS 默认提供的多种账户存储实现及加载机制、账户模型PasswordlessUserAccount的全部字段语义,以及如何在 CAS Overlay 中以源码级方式编写并注册自定义存储,让无密码登录直接对接你自有目录中的用户数据。

自定义用户账户存储:核心思路

在 CAS 的 Passwordless 认证流程中,当用户提交用户名后,CAS 需要一个"账户查找"环节来决定该用户是否可以发起无密码认证,以及应当向哪个联系方式(邮箱/手机号)发送一次性令牌。这一查找行为被抽象为PasswordlessUserAccountStore接口。

官方文档 Passwordless-Authentication-Storage-Custom.md 明确指出:你可以定义自己的用户账户存储,只需实现PasswordlessUserAccountStore接口,并以如下 Bean 定义将其注册到 CAS 运行时:

@Bean public PasswordlessUserAccountStore passwordlessUserAccountStore() { ... }

这里有两个关键点需要留意:

  1. Bean 名称必须是passwordlessUserAccountStore。该名称正是接口中声明的常量BEAN_NAME(见 PasswordlessUserAccountStore.java),CAS 的自动配置在装配默认存储时使用了@ConditionalOnMissingBean(name = PasswordlessUserAccountStore.BEAN_NAME)进行守卫(见 CasPasswordlessAuthenticationAutoConfiguration.java)。只要你的自定义 Bean 以同名注册,CAS 就会跳过内部默认组装逻辑,直接使用你的实现。
  2. 注册位置:自定义配置类需要能被 CAS 的@AutoConfiguration机制扫描到。具体注册方法参见 Configuration-Management-Extensions.md:将配置类全限定名写入src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件即可。

接口定义与契约

PasswordlessUserAccountStore位于support/cas-server-support-passwordless-api模块,是一个标注了@FunctionalInterface的接口,源码见 PasswordlessUserAccountStore.java:

@FunctionalInterface public interface PasswordlessUserAccountStore { String BEAN_NAME = "passwordlessUserAccountStore"; Optional<? extends PasswordlessUserAccount> findUser(PasswordlessAuthenticationRequest request) throws Throwable; default void reload() { } }

接口契约包含两部分:

  • findUser(PasswordlessAuthenticationRequest request):唯一必须实现的方法。入参是封装了用户名的PasswordlessAuthenticationRequest,返回Optional<? extends PasswordlessUserAccount>。返回Optional.empty()表示未找到该用户,CAS 将拒绝发起无密码认证;返回账户对象则表示命中,后续流程将据此发送令牌。
  • reload():默认空实现,用于支持账户数据的运行时刷新。例如基于 JSON 文件的存储实现会通过它重读文件内容(见下文)。如果你的自定义存储有缓存或外部数据源变更场景,建议重写该方法。

请求对象:PasswordlessAuthenticationRequest

findUser的入参类型为 PasswordlessAuthenticationRequest.java,自 7.0.0 版本引入,字段如下:

字段类型说明
usernameString用户在登录页面提交的用户名(必填)
providedUsernameString额外提供的用户名信息(可为空)
propertiesMap<String, String>附加请求属性集合,可用于传递上下文信息

账户模型:PasswordlessUserAccount

返回的账户对象类型为 PasswordlessUserAccount.java,其字段决定了无密码认证的后续行为:

字段类型默认值语义
usernameString-用户名
emailString-邮箱,令牌将发送至此(需与phone至少提供一个)
phone(JSON 字段名phoneNumber)String-手机号,令牌发送的备选渠道
nameString-显示名称
attributesMap<String, List<Object>>空LinkedHashMap用户属性,将并入最终认证主体(Principal)
multifactorAuthenticationEligibleTriStateBooleanUNDEFINED是否允许/要求多因素认证(三态布尔,可显式 true/false 或保持未定义)
delegatedAuthenticationEligibleTriStateBooleanUNDEFINED是否允许委托认证(如通过外部 IdP 登录)
allowedDelegatedClientsList<String>空列表允许使用的委托客户端白名单
requestPasswordbooleanfalse是否要求用户额外输入密码
allowSelectionMenubooleanfalse是否允许显示账户选择菜单
sourceString-账户来源标识

其中hasContactInformation()方法用于校验账户是否具备联系方式(email或phone至少一个非空),这是 CAS 判断能否发送令牌的依据。该模型使用 Jackson 序列化(@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)保留类型信息),因此从 REST 或 JSON 数据源反序列化时,返回结构需要与该字段集合兼容。

CAS 内置的账户存储实现

在动手编写自定义实现之前,先了解 CAS 自带的几种PasswordlessUserAccountStore实现,它们既可以直接使用,也可以作为自定义实现的参考模板。全部位于support/cas-server-support-passwordless-api/src/main/java/org/apereo/cas/impl/account/目录:

实现类数据来源触发配置
SimplePasswordlessUserAccountStore配置文件中的键值对cas.authn.passwordless.accounts.simple.*
JsonPasswordlessUserAccountStoreJSON 文件cas.authn.passwordless.accounts.json.location
GroovyPasswordlessUserAccountStoreGroovy 脚本cas.authn.passwordless.accounts.groovy.location
RestfulPasswordlessUserAccountStoreREST 接口cas.authn.passwordless.accounts.rest.url

组合器:ChainingPasswordlessAccountStore

需要注意,CAS 默认装配的passwordlessUserAccountStore并非上述某一个实现,而是一个组合存储ChainingPasswordlessAccountStore.java:自动配置会收集容器中所有PasswordlessUserAccountStore类型的 Bean(含内置的 JSON/Groovy/REST/Simple 存储与用户自定义存储),按AnnotationAwareOrderComparator排序后包装为链式查找。findUser依次询问每个子存储,返回第一个命中结果(见 CasPasswordlessAuthenticationAutoConfiguration.java)。

这意味着:

  • 如果你自定义的存储 Bean使用了其他名称(而非passwordlessUserAccountStore),它会被自动纳入默认链式存储中作为补充数据源;
  • 如果你自定义的 Bean使用标准名称passwordlessUserAccountStore,则完全替换掉默认链式存储,此时你需要自行处理多数据源逻辑。

SimplePasswordlessUserAccountStore:配置驱动的极简实现

该实现内部维护Map<String, PasswordlessUserAccount> accounts,findUser时使用正则匹配:以配置中的 key 作为正则表达式,对请求用户名做RegexUtils.find(key, username)匹配,命中即返回对应账户(见 SimplePasswordlessUserAccountStore.java)。由于 key 被当作正则处理,你甚至可以用user.*之类的模式批量匹配用户。

其对应的测试用例 SimplePasswordlessUserAccountStoreTests.java 展示了最简配置形态:

cas.authn.passwordless.accounts.simple.casuser=1234567890 cas.authn.passwordless.tokens.crypto.enabled=false

配置值会被自动识别为邮箱或手机号:EmailValidator判定为合法邮箱则设置email,否则设置phone(见 CasPasswordlessAuthenticationAutoConfiguration.java)。测试中casuser查询返回账户、other查询返回空,验证了查找与 miss 两条路径。

JsonPasswordlessUserAccountStore:文件热加载

该实现继承SimplePasswordlessUserAccountStore,从 JSON 文件读取账户 Map,并通过FileWatcherService监控文件变化,一旦文件被修改就触发reload()重新加载(见 JsonPasswordlessUserAccountStore.java)。JSON 文件结构与账户模型对应,例如:

{ "casuser": { "username": "casuser", "email": "casuser@example.org", "phoneNumber": "1234567890", "name": "CAS User", "attributes": { "memberOf": ["staff"] }, "requestPassword": false, "allowSelectionMenu": false } }

GroovyPasswordlessUserAccountStore:脚本驱动

以 Groovy 脚本作为查找逻辑,脚本入参为[request, logger]两个对象,返回PasswordlessUserAccount或空(见 GroovyPasswordlessUserAccountStore.java)。脚本位置通过cas.authn.passwordless.accounts.groovy.location指定,适合快速实现复杂查找规则而无需重新编译 Java 代码。

RestfulPasswordlessUserAccountStore:REST 数据源

将查找行为委托给远程 REST 接口:CAS 以配置的 HTTP 方法请求url + "/" + username,附带username请求参数与自定义 headers,响应体按PasswordlessUserAccount.class反序列化(支持 HJSON 解析),返回2xx且有实体时即视为命中(见 RestfulPasswordlessUserAccountStore.java)。相关配置包括:

cas.authn.passwordless.accounts.rest.url=https://idm.example.org/api/passwordless/account cas.authn.passwordless.accounts.rest.method=GET cas.authn.passwordless.accounts.rest.basic-auth-username=user cas.authn.passwordless.accounts.rest.basic-auth-password=secret cas.authn.passwordless.accounts.rest.headers=... cas.authn.passwordless.accounts.rest.maximum-retry-attempts=3

编写并注册自定义存储:完整实战

下面以"对接企业自有用户目录"为场景,演示完整的自定义实现步骤。假设你的用户目录存储在一个内存 Map 或数据库中,需要根据用户名返回PasswordlessUserAccount。

第一步:实现接口

在 CAS Overlay 中新建配置类与存储实现:

package org.example.cas.config; import org.apereo.cas.api.PasswordlessAuthenticationRequest; import org.apereo.cas.api.PasswordlessUserAccount; import org.apereo.cas.api.PasswordlessUserAccountStore; import org.apereo.cas.authentication.principal.PrincipalFactory; import java.util.Optional; public class MyDirectoryPasswordlessUserAccountStore implements PasswordlessUserAccountStore { private final UserDirectoryService userDirectoryService; public MyDirectoryPasswordlessUserAccountStore(final UserDirectoryService userDirectoryService) { this.userDirectoryService = userDirectoryService; } @Override public Optional<? extends PasswordlessUserAccount> findUser( final PasswordlessAuthenticationRequest request) throws Throwable { final String username = request.getUsername(); final UserRecord record = userDirectoryService.findByUsername(username); if (record == null) { return Optional.empty(); } return Optional.of(PasswordlessUserAccount.builder() .username(record.getUsername()) .email(record.getEmail()) .phone(record.getPhoneNumber()) .name(record.getDisplayName()) .attributes(Map.of("memberOf", List.of(record.getGroups()))) .requestPassword(false) .build()); } @Override public void reload() { userDirectoryService.refreshCache(); } }

第二步:声明 Bean 并注册配置类

参照官方文档给出的 Bean 定义,并在配置类上补充@AutoConfiguration与@EnableConfigurationProperties:

package org.example.cas.config; import org.apereo.cas.api.PasswordlessUserAccountStore; import org.apereo.cas.configuration.CasConfigurationProperties; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ScopedProxyMode; @AutoConfiguration @EnableConfigurationProperties(CasConfigurationProperties.class) public class CustomPasswordlessAccountStoreConfiguration { @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) @Bean(name = PasswordlessUserAccountStore.BEAN_NAME) public PasswordlessUserAccountStore passwordlessUserAccountStore( @Qualifier("userDirectoryService") final UserDirectoryService userDirectoryService) { return new MyDirectoryPasswordlessUserAccountStore(userDirectoryService); } }

要点说明:

  • @Bean(name = PasswordlessUserAccountStore.BEAN_NAME):显式使用标准 Bean 名称,从而触发@ConditionalOnMissingBean(name = "passwordlessUserAccountStore")条件,使你的实现完全替换默认链式存储。
  • @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT):与 CAS 内部 Bean 定义保持一致,外部配置变更触发上下文刷新时 Bean 可被重建(CAS 官方文档 Configuration-Management-Extensions.md 亦推荐此写法)。
  • 若你希望自定义存储作为补充数据源参与默认链式查找,则去掉name属性(改用如myPasswordlessUserAccountStore的自定义名称),并可通过@Order(...)控制查找顺序。

第三步:注册到 AutoConfiguration.imports

创建(或编辑)src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,加入配置类全限定名:

org.example.cas.config.CustomPasswordlessAccountStoreConfiguration

重启 CAS 后,passwordlessUserAccountStore即为你自定义的实现。你可以通过 CAS 暴露的账户查询端点验证效果:GET /cas/actuator/passwordless/account?username=casuser(端点实现见 PasswordlessAuthenticationEndpoint.java)。命中时返回账户 JSON(200),未命中返回 404;同时端点会用默认PrincipalResolver解析用户并尝试将解析出的属性合并进账户attributes,方便你观察最终账户数据。

进阶:账户定制器(PasswordlessUserAccountCustomizer)

除存储实现外,CAS 还提供了PasswordlessUserAccountCustomizer扩展点,用于在存储查找完成后对账户做二次加工(见 PasswordlessUserAccountCustomizer.java)。它是一个Ordered接口,customize(Optional<? extends PasswordlessUserAccount>)返回处理后的账户,默认getOrder()为Ordered.HIGHEST_PRECEDENCE。

SimplePasswordlessUserAccountStore的findUser在命中账户后会对所有非代理的 customizer 依次调用(见 SimplePasswordlessUserAccountStore.java)。CAS 内置的GroovyPasswordlessUserAccountCustomizer(源码)允许通过cas.authn.passwordless.core.passwordless-account-customizer-script.location指定 Groovy 脚本来动态修改账户属性,脚本入参为[account, applicationContext, logger]。

如果你的自定义存储想复用 customizer 机制,可参考内置实现的做法:在构造器中注入List<PasswordlessUserAccountCustomizer>,在findUser命中后遍历调用。

存储查找链路与认证衔接

为了完整理解自定义存储在整个无密码认证中的位置,梳理关键调用链如下:

  1. 用户在登录页输入用户名,CAS 触发无密码认证流程;
  2. PasswordlessUserAccountStore.findUser(request)被调用(可能经由ChainingPasswordlessAccountStore链式分发到你的自定义存储);
  3. 命中账户后,CAS 依据账户的email/phone发送一次性令牌,令牌由PasswordlessTokenRepository管理(默认内存实现,可配置 REST 后端,见 CasPasswordlessAuthenticationAutoConfiguration.java);
  4. 用户提交令牌,PasswordlessTokenAuthenticationHandler校验通过后完成认证,账户attributes并入最终 Principal。

从源码结构可以推断,passwordlessUserAccountStoreBean 同时被认证处理器、端点等组件通过@Qualifier(PasswordlessUserAccountStore.BEAN_NAME)引用,因此只要标准 Bean 存在且行为正确,整个认证链无需其他改动。

小结

自定义 Passwordless 用户账户存储是 CAS 无密码认证落地自有用户体系的标准做法,核心就三步:实现PasswordlessUserAccountStore接口、以标准 Bean 名称注册、通过AutoConfiguration.imports让 CAS 发现配置类。本文涉及的接口与实现均可在仓库中直接研读:接口契约见 PasswordlessUserAccountStore.java,账户模型见 PasswordlessUserAccount.java,自动装配逻辑见 CasPasswordlessAuthenticationAutoConfiguration.java,内置实现与测试见support/cas-server-support-passwordless-api/src/main/java/org/apereo/cas/impl/account/目录及 SimplePasswordlessUserAccountStoreTests.java。官方扩展机制的整体说明可参考 Configuration-Management-Extensions.md。

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

【免费下载链接】cas

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

项目地址:https://gitcode.com/gh_mirrors/ca/cas
点击查看免费下载
上一篇:motionEye 性能调优技巧:提升视频流处理效率的终极指南
下一篇:终极指南:如何在Termux上快速安装和修复Metasploit

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:01:10

Open Code Review:可审计、可验证的开源代码审查范式

1. 这不是又一个“AI代码审查工具”&#xff0c;而是一套可审计、可验证、可嵌入CI的开源协作范式 你有没有遇到过这样的场景&#xff1a;团队里新来一位 junior 开发者&#xff0c;提交了一段看似逻辑通顺的 Python 脚本——它能跑通单元测试&#xff0c;也能在本地环境输出预…

作者头像 李华
网站建设 2026/9/26 2:00:27

Oracle EBS AP预付款管理:从创建到核销的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:00:27

【亲测免费】 Pyxel - 一个复古风格的游戏开发框架

Pyxel - 一个复古风格的游戏开发框架 【免费下载链接】pyxel A retro game engine for Python 项目地址: https://gitcode.com/GitHub_Trending/py/pyxel 是一个由 Kitao 制作的 Python 库&#xff0c;它旨在简化2D游戏的开发过程&#xff0c;并提供了一种独特的复古视觉…

作者头像 李华
网站建设 2026/9/26 1:59:36

C语言-----程序控制结构和语句(2)

目录 1、循环结构与语句 1.1 循环结构 1.2 当型循环语句----- for 语句 1.3 当型循环语句----- while 语句 1.4 直到型循环语句----- do-while 语句 1.5 循环的嵌套 2、转向语句 2.1 goto 语句 2.2 break 语句 2.3 continue 语句 本章博客是对程序控制结构和…

作者头像 李华
网站建设 2026/9/26 1:59:34

C语言​-----格式字符、整型、字符型、浮点型

1、格式字符​格式字符是由“%”和字符组成&#xff0c;其作用是将输出的数据转化为指定的格式输出。格式字符表如下&#xff1a;%d/%i有符号的十进制整数&#xff0c;i 是老式写法%u无符号十进制整数%c字符%s字符串%f单精度浮点数%lf双精度浮点数(lf 在 C99 开始加入标准&…

作者头像 李华