- 后端
- 认证鉴权
- 单点登录
【免费下载链接】cas
Apereo CAS - Identity & Single Sign On for all earthlings and beyond.
GUA(Graphical User Authentication)是 Apereo CAS 提供的一种"以图识人"式认证增强:在正式登录前,先展示与用户绑定的图形(如本人照片或自定义标识),由用户确认身份后进入凭据认证环节。本文聚焦其中最简单的一种实现——静态资源(Static Resource)GUA:将一张全局静态图片直接作为用户标识加载进登录流程,主要用于演示(demo)与测试目的,配置上仅依赖cas.authn.gua.simple一个属性组。读完本文,你将掌握静态 GUA 的配置方法、配置属性的取值规则、其背后的仓库接口与 Webflow 执行链路,以及它与 LDAP 存储方案的选型边界。
什么是 GUA 与静态资源模式
GUA 的全称是 Graphical User Authentication。它的核心思想不是替代密码,而是在主认证(primary authentication)之前增加一步"图像确认":
- 用户输入用户名(或由系统预先识别);
- CAS 从存储介质中取出与该用户名绑定的图形资源并展示在页面上;
- 用户确认图形无误后,流程继续进入正常的凭据认证(如用户名/密码)。
从源码结构看,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); }也就是说:
- 只要
cas.authn.gua.simple非空(配置了至少一个用户),CAS 就会启用静态仓库; - 每个
value会通过ResourceUtils.getResourceFrom(...)解析为 SpringResource(因此classpath:、file:、url:前缀均受支持); - 最终构建出
Map<String, Resource>,交给StaticUserGraphicalAuthenticationRepository。
若simple为空,则转而检查 LDAP 方案所需的ldapUrl、searchFilter、baseDn、imageAttribute等属性;如果两者都没有配置,启动时直接抛出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 / 状态 | 职责 |
|---|---|---|
| 准备 | PrepareForGraphicalAuthenticationAction | 在initLoginForm状态执行前挂载,将"启用 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:initLoginForm→guaGetUserId→guaDisplayUserGraphics→acceptGua→ 原登录流程。
与 LDAP 存储方案的对比
虽然本文主题是静态资源方案,但理解它与另一方案的边界有助于正确选型。从 CasGraphicalUserAuthenticationAutoConfiguration 可以看出,LDAP 方案要求同时配置ldapUrl、searchFilter、baseDn、imageAttribute四个属性,此时才会创建LdapUserGraphicalAuthenticationRepository,通过 LDAP 连接工厂按用户搜索并读取imageAttribute属性中存储的图片。
| 维度 | 静态资源模式(本文) | LDAP 模式 |
|---|---|---|
| 配置入口 | cas.authn.gua.simple(Map) | cas.authn.gua.ldap(嵌套属性) |
| 图片来源 | 全局静态图片,按用户名映射 | LDAP 目录中的用户属性(二进制) |
| 适用场景 | 演示、测试 | 已有 LDAP 目录、图片入目录的企业环境 |
| 实现类 | StaticUserGraphicalAuthenticationRepository | LdapUserGraphicalAuthenticationRepository |
启用 GUA 模块的前置条件
静态 GUA 作为独立 feature 生效,还需要满足以下条件:
- 依赖模块:部署包中必须包含
cas-server-support-gua模块(Gradle 依赖"org.apereo.cas:cas-server-support-gua")。这是配置模型@RequiresModule(name = "cas-server-support-gua")声明的硬性要求; - Feature 开关:自动装配类使用
@ConditionalOnFeatureEnabled(feature = CasFeatureModule.FeatureCatalog.Authentication, module = "gua")控制(见 CasGraphicalUserAuthenticationAutoConfiguration),需要在运行时启用Authentication类目下的guafeature; - 仓库配置:
simple或ldap至少配置一种,否则启动时抛BeanCreationException; - 视图资源:登录流程依赖
gua/casGuaGetUserIdView与gua/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.
相关推荐
Apereo CAS 基础认证(Basic Authentication)接入指南:配置、源码原理与 curl 实践
Apereo CAS 基础认证(Basic Authentication)接入指南:配置、源码原理与 curl 实践 Apereo CAS 的基础认证(Basi
后端认证鉴权单点登录Apereo CAS 用户界面定制:静态主题配置指南
Apereo CAS 用户界面定制:静态主题配置指南 什么是静态主题? 在Apereo CAS中,静态主题是一种在不改变视图物理结构的前提下,通过CSS和Jav
后端认证鉴权单点登录Apereo CAS 集成 Apache Cassandra 认证:配置详解与源码剖析
Apereo CAS 集成 Apache Cassandra 认证:配置详解与源码剖析 本文基于 Cassandra Authentication.md htt
后端认证鉴权单点登录
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考