Apereo CAS 静态资源图形用户认证(GUA)配置与源码原理指南
2026/9/24 14:51:34 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

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

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

GUA(Graphical User Authentication)是 Apereo CAS 提供的一种"以图识人"式认证增强:在正式登录前,先展示与用户绑定的图形(如本人照片或自定义标识),由用户确认身份后进入凭据认证环节。本文聚焦其中最简单的一种实现——静态资源(Static Resource)GUA:将一张全局静态图片直接作为用户标识加载进登录流程,主要用于演示(demo)与测试目的,配置上仅依赖cas.authn.gua.simple一个属性组。读完本文,你将掌握静态 GUA 的配置方法、配置属性的取值规则、其背后的仓库接口与 Webflow 执行链路,以及它与 LDAP 存储方案的选型边界。

什么是 GUA 与静态资源模式

GUA 的全称是 Graphical User Authentication。它的核心思想不是替代密码,而是在主认证(primary authentication)之前增加一步"图像确认":

  1. 用户输入用户名(或由系统预先识别);
  2. CAS 从存储介质中取出与该用户名绑定的图形资源并展示在页面上;
  3. 用户确认图形无误后,流程继续进入正常的凭据认证(如用户名/密码)。

从源码结构看,GUA 功能被独立封装在support/cas-server-support-gua模块中,其模块说明与配置模型类 GraphicalUserAuthenticationProperties 的注释一致:"identification of users graphically prior to executing primary authn"——即在执行主认证之前对用户进行图形化识别。

静态资源模式是 GUA 的两种存储方案之一(另一种是 LDAP 方案)。原文档明确指出该模式:

Primarily useful for demo and testing purposes, this option allows CAS to load a global and static image resource as the user identifier onto the login flow.

即它加载的是一个全局、静态的图片资源,不存在按用户区分的多图机制,因此非常适合在演示环境与自动化测试中快速验证 GUA 的交互链路,而不适合生产环境的多用户真实场景。

配置静态 GUA:cas.authn.gua.simple

静态 GUA 的全部配置都落在cas.authn.gua.simple属性组上。根据配置模型 GraphicalUserAuthenticationProperties 的定义:

/** * Locate GUA settings and images from a static image per user. * This is treated as a {@link Map} where the key is the user id * and the value should be the graphical resource. */ private Map<String, String> simple = new LinkedHashMap<>();

simple是一个Map<String, String>

  • key(键):用户名(user id);
  • value(值):图形资源的定位串(resource location),可以是classpath:file:url:前缀的资源路径。

一个最小可用的配置示例如下(以application.properties为例):

# 将 classpath 下的图片绑定到用户 casuser cas.authn.gua.simple.casuser=classpath:images/casuser.jpg # 也可以使用文件系统路径 cas.authn.gua.simple.casuser=file:/etc/cas/config/images/casuser.jpg # 还可以指向 HTTP 资源 cas.authn.gua.simple.casuser=https://example.org/images/casuser.jpg

源码如何解析这些路径

在自动装配类 CasGraphicalUserAuthenticationAutoConfiguration 中,simple映射的解析逻辑非常清晰:

val gua = casProperties.getAuthn().getGua(); if (!gua.getSimple().isEmpty()) { val accounts = gua.getSimple().entrySet().stream().map(Unchecked.function(entry -> { val res = ResourceUtils.getResourceFrom(entry.getValue()); return Pair.of(entry.getKey(), (Resource) res); })).collect(Collectors.toMap(Pair::getKey, Pair::getValue)); return new StaticUserGraphicalAuthenticationRepository(accounts); }

也就是说:

  1. 只要cas.authn.gua.simple非空(配置了至少一个用户),CAS 就会启用静态仓库;
  2. 每个value会通过ResourceUtils.getResourceFrom(...)解析为 SpringResource(因此classpath:file:url:前缀均受支持);
  3. 最终构建出Map<String, Resource>,交给StaticUserGraphicalAuthenticationRepository

simple为空,则转而检查 LDAP 方案所需的ldapUrlsearchFilterbaseDnimageAttribute等属性;如果两者都没有配置,启动时直接抛出BeanCreationException,提示"A repository instance must be configured to locate user-defined graphics"。这意味着静态与 LDAP 两种仓库必须且只能选择其一

静态仓库实现:StaticUserGraphicalAuthenticationRepository

静态模式的核心实现类是 StaticUserGraphicalAuthenticationRepository,它实现了统一接口 UserGraphicalAuthenticationRepository。该接口只有一个方法:

@FunctionalInterface public interface UserGraphicalAuthenticationRepository extends Serializable { ByteSource getGraphics(String username); }

getGraphics(String username)接收用户名,返回图片的二进制字节流(Google Guava 的ByteSource)。静态实现通过Map<String, Resource>直接按键取值:

@Override public ByteSource getGraphics(final String username) { try (val resourceStream = graphicResource.get(username).getInputStream(); val bos = new ByteArrayOutputStream()) { IOUtils.copy(resourceStream, bos); return ByteSource.wrap(bos.toByteArray()); } catch (final Exception e) { LoggingUtils.error(LOGGER, e); } return ByteSource.empty(); }

值得注意的两个实现细节:

  • 键不存在或读取失败时不会抛异常,而是返回ByteSource.empty()(空字节流)。这一点与仓库单元的测试用例相印证:StaticUserGraphicalAuthenticationRepositoryTests 中verifyBadImage用例正是用不存在的missing.jpg验证了"返回空流"的行为;
  • 图片以原始字节流形式读出,在后续 Webflow 动作中再统一做 Base64 编码,从而兼容各种图片格式(JPG、PNG 等)。

登录流程:三个 Action 与两个视图状态

静态图片要进入登录流程,依赖 Webflow 配置器 GraphicalUserAuthenticationWebflowConfigurer 对登录流程的改造。整个交互由三个 Action 与两个视图状态串联:

阶段Action / 状态职责
准备PrepareForGraphicalAuthenticationActioninitLoginForm状态执行前挂载,将"启用 GUA"标志写入流程上下文
输入casGuaGetUserIdView视图状态展示"输入用户名"页面(gua/casGuaGetUserIdView
展示DisplayUserGraphicsBeforeAuthenticationAction按用户名取图、Base64 编码并放入流程上下文,渲染casGuaDisplayUserGraphicsView
确认AcceptUserGraphicsForAuthenticationAction用户确认图形后,流转回原登录表单的成功目标状态

1. 准备动作:决定走不走 GUA 分支

PrepareForGraphicalAuthenticationAction 逻辑极简:

WebUtils.putGraphicalUserAuthenticationEnabled(requestContext, Boolean.TRUE); if (!WebUtils.containsGraphicalUserAuthenticationUsername(requestContext)) { return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_GUA_GET_USERID); } return null;

它总是声明"GUA 已启用",且只有流程上下文中还没有 GUA 用户名时才跳转到guaGetUserId状态(要求输入用户名);一旦用户名已经存在于上下文中(例如由casGuaGetUserIdView提交而来),则不产生事件,流程自然继续。

2. 展示动作:取图并做 Base64 编码

DisplayUserGraphicsBeforeAuthenticationAction 是取图的核心:

val username = requestContext.getRequestParameters().get("username"); if (StringUtils.isBlank(username)) { throw UnauthorizedServiceException.denied("Denied"); } val graphics = repository.getGraphics(username); if (graphics == null || graphics.isEmpty()) { throw UnauthorizedServiceException.denied("Denied"); } val image = EncodingUtils.encodeBase64ToByteArray(graphics.read()); WebUtils.putGraphicalUserAuthenticationUsername(requestContext, username); WebUtils.putGraphicalUserAuthenticationImage(requestContext, new String(image, StandardCharsets.UTF_8)); return success();

两个安全细节值得强调:

  • 用户名为空,或仓库返回的图片为空(用户名未配置、资源读取失败),都会抛出UnauthorizedServiceException,流程被拒绝,而不会展示空白图片;
  • 图片被EncodingUtils.encodeBase64ToByteArray编码为 Base64 字符串后,通过WebUtils.putGraphicalUserAuthenticationImage放入流程上下文,供casGuaDisplayUserGraphicsView视图渲染时直接以内联 data URI 形式使用。

3. 确认动作与流程收尾

AcceptUserGraphicsForAuthenticationAction代表用户点击"确认图形"后的动作,执行完毕后通过createStateDefaultTransition(acceptState, targetStateId)回到原先initLoginForm状态success转换所指向的目标状态,从而无缝衔接正常的登录表单认证。完整的 Webflow 拓扑可参考 GraphicalUserAuthenticationWebflowConfigurer:initLoginFormguaGetUserIdguaDisplayUserGraphicsacceptGua→ 原登录流程。

与 LDAP 存储方案的对比

虽然本文主题是静态资源方案,但理解它与另一方案的边界有助于正确选型。从 CasGraphicalUserAuthenticationAutoConfiguration 可以看出,LDAP 方案要求同时配置ldapUrlsearchFilterbaseDnimageAttribute四个属性,此时才会创建LdapUserGraphicalAuthenticationRepository,通过 LDAP 连接工厂按用户搜索并读取imageAttribute属性中存储的图片。

维度静态资源模式(本文)LDAP 模式
配置入口cas.authn.gua.simple(Map)cas.authn.gua.ldap(嵌套属性)
图片来源全局静态图片,按用户名映射LDAP 目录中的用户属性(二进制)
适用场景演示、测试已有 LDAP 目录、图片入目录的企业环境
实现类StaticUserGraphicalAuthenticationRepositoryLdapUserGraphicalAuthenticationRepository

启用 GUA 模块的前置条件

静态 GUA 作为独立 feature 生效,还需要满足以下条件:

  1. 依赖模块:部署包中必须包含cas-server-support-gua模块(Gradle 依赖"org.apereo.cas:cas-server-support-gua")。这是配置模型@RequiresModule(name = "cas-server-support-gua")声明的硬性要求;
  2. Feature 开关:自动装配类使用@ConditionalOnFeatureEnabled(feature = CasFeatureModule.FeatureCatalog.Authentication, module = "gua")控制(见 CasGraphicalUserAuthenticationAutoConfiguration),需要在运行时启用Authentication类目下的guafeature;
  3. 仓库配置simpleldap至少配置一种,否则启动时抛BeanCreationException
  4. 视图资源:登录流程依赖gua/casGuaGetUserIdViewgua/casGuaDisplayUserGraphicsView两个 Thymeleaf 视图,它们随cas-server-support-gua模块打包提供。

注意事项与限制

  • 全局单一图片语义:从配置模型看,simple是"每个用户名映射一张图片",但其定位仍是"全局静态资源"——同一张图(或同一批写死的图)对所有用户可见,因此仅适合演示与测试;
  • 键不存在即拒绝:未在simple中配置的用户名,其图片读取结果为空,DisplayUserGraphicsBeforeAuthenticationAction会直接抛出UnauthorizedServiceException,表现为"Denied";
  • 图片格式:静态仓库对图片格式无约束,读取后按 Base64 编码交给前端渲染,JPG/PNG 均可,测试用例中使用的即是classpath:image.jpg
  • 资源热更新:仓库 Bean 使用了@RefreshScope,意味着simple映射支持配置刷新(refresh),但StaticUserGraphicalAuthenticationRepository内部持有的是构建时的Map<String, Resource>,运行时修改配置需配合配置刷新机制生效;
  • 生产选型建议:多用户、按用户存储图片的真实场景应优先评估 LDAP 方案(见 LdapUserGraphicalAuthenticationProperties),静态模式请勿用于生产环境。

参考源码与文档

  • 本文对应的原始文档:GUA-Authentication-Storage-Resource.md
  • 配置模型:GraphicalUserAuthenticationProperties.java
  • 自动装配与仓库选型:CasGraphicalUserAuthenticationAutoConfiguration.java
  • 仓库接口与静态实现:UserGraphicalAuthenticationRepository.java、StaticUserGraphicalAuthenticationRepository.java
  • Webflow 配置与动作:GraphicalUserAuthenticationWebflowConfigurer.java、DisplayUserGraphicsBeforeAuthenticationAction.java
  • 单元测试:StaticUserGraphicalAuthenticationRepositoryTests.java
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

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

项目地址:https://gitcode.com/gh_mirrors/ca/cas
点击查看免费下载
上一篇:Cerebro VNC插件:终极跨平台远程桌面解决方案指南
下一篇:ng-sortable性能优化指南:如何避免拖放操作中的常见性能陷阱

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

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

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

立即咨询