news 2026/9/24 9:51:26

Apereo CAS 全局认证策略(Global Authentication Policy)配置与自定义扩展指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Apereo CAS 全局认证策略(Global Authentication Policy)配置与自定义扩展指南
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

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

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

本文聚焦 Apereo CAS 中的全局认证策略(Global Authentication Policy):它作用于 CAS 发行(vend)与验证(validate)票据的全过程,决定一次认证事件在何种条件下被判定为满足安全策略。读完本文,你将掌握cas.authn.policy.required-handler-authentication-policy-enabled内置策略的开启方式与底层实现原理,能够通过定义globalAuthenticationPolicyBean 编写自己的全局认证策略,并理解全局策略与服务级认证策略(requiredAuthenticationHandlers)之间的协作关系。

什么是全局认证策略

在 CAS 中,认证策略(Authentication Policy)负责回答两个核心问题:

  1. 认证链(authentication chain)在发生某类失败后是否应该停止?
  2. 当一条认证链中存在多个认证处理器(authentication handler)时,怎样才算一次成功的认证事件?

全局认证策略是其中作用范围最广的一类:如 Configuring-Authentication-Policy-Global.md 所述,它是"当 CAS 尝试发行票据(vend tickets)和验证票据(validate tickets)时被应用的全局认证策略"。

从源码上看,这一策略被注入到CentralAuthenticationService的运行时上下文中。在 CasCoreConfiguration.java 中,globalAuthenticationPolicy这个AuthenticationPolicyBean 被通过@Qualifier("globalAuthenticationPolicy")注入centralAuthenticationServiceContext,最终成为CentralAuthenticationServiceContext.authenticationPolicy。也就是说,所有经CentralAuthenticationService处理的票据发行、服务票据(Service Ticket)授予等关键路径,都会受该策略约束。

与全局策略相对应的是服务级认证策略:每个注册应用(registered service)也可以声明自己的认证策略,二者可能互补,也可能由服务级策略覆盖全局行为。详见 Configuring-Service-AuthN-Policy.md。

策略的执行时机与调用链

全局认证策略并不是在认证刚发生时立即判定,而是在认证链执行完毕、票据将要发行/验证时进行复核。策略执行的核心入口位于 AbstractCentralAuthenticationService.java 的getAuthenticationSatisfiedByPolicy方法:

protected @Nullable Authentication getAuthenticationSatisfiedByPolicy( @Nullable final Authentication authentication, @Nullable final Service service, @Nullable final RegisteredService registeredService) throws AbstractTicketException { val policy = configurationContext.getAuthenticationPolicy(); try { val policyContext = Map.of(RegisteredService.class.getName(), Objects.requireNonNull(registeredService), Service.class.getName(), Objects.requireNonNull(service)); val executionResult = policy.isSatisfiedBy(authentication, configurationContext.getApplicationContext(), policyContext); if (executionResult.isSuccess()) { return authentication; } } catch (final Throwable e) { LoggingUtils.error(LOGGER, e); } throw new UnsatisfiedAuthenticationPolicyException(policy); }

这段代码揭示了三个要点:

  • 策略上下文(policyContext)中包含当前RegisteredServiceService,因此全局策略可以感知"本次认证是针对哪个注册应用";
  • 判定通过时返回原Authentication对象,流程继续;
  • 判定失败时抛出UnsatisfiedAuthenticationPolicyException,阻止票据的发行或验证。

该入口在 DefaultCentralAuthenticationService.java 中被多处调用:授予服务票据(L449)、创建票据授予票据(L218)以及代理授予票据路径(L131),正好印证了文档中"应用在 CAS 发行和验证票据时"的描述。

内置全局策略:必需认证处理器策略

开启方式

CAS 内置的全局认证策略由以下属性控制:

cas.authn.policy.required-handler-authentication-policy-enabled=true

该属性定义在 AuthenticationPolicyProperties.java 中,注释明确说明:

Global authentication policy that is applied when CAS attempts to vend and validate tickets. Checks to make sure a particular authentication handler has successfully executed and validated credentials. Required handlers are defined per registered service.

即:启用后,CAS 会检查某个特定的认证处理器是否已成功执行并验证了凭证;"必需处理器"是按注册服务(per registered service)定义的。

从配置模型看,该布尔开关挂在cas.authn.policy命名空间下,由 AuthenticationProperties.java 中的AuthenticationPolicyProperties policy字段承载。

底层实现原理

当开关打开时,CasCoreConfiguration.java 中的globalAuthenticationPolicyBean 会返回一个"必需处理器判定"的 lambda 实现:

@Bean @ConditionalOnMissingBean(name = "globalAuthenticationPolicy") @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) public AuthenticationPolicy globalAuthenticationPolicy(final CasConfigurationProperties casProperties) { if (casProperties.getAuthn().getPolicy().isRequiredHandlerAuthenticationPolicyEnabled()) { LOGGER.trace("Applying configuration for Required Handler Authentication Policy"); return (authentication, handlers, applicationContext, context) -> { val registeredService = (RegisteredService) context.get(RegisteredService.class.getName()); val requiredHandlers = registeredService.getAuthenticationPolicy().getRequiredAuthenticationHandlers(); LOGGER.debug("Required authentication handlers for this service [{}] are [{}]", registeredService.getName(), requiredHandlers); val success = requiredHandlers .stream() .allMatch(required -> authentication.getSuccesses().containsKey(required)); return AuthenticationPolicyExecutionResult.success(success); }; } return AuthenticationPolicy.alwaysSatisfied(); }

实现逻辑可拆解为:

  1. 从策略上下文取出当前RegisteredService
  2. 读取该服务定义的requiredAuthenticationHandlers(必需认证处理器集合);
  3. 逐一检查当前Authenticationsuccesses集合中是否包含这些必需处理器——successes以认证处理器名称为键;
  4. 只有当全部必需处理器都出现在成功记录中时,策略才被满足(allMatch),否则判定失败。

当开关未启用时,默认返回AuthenticationPolicy.alwaysSatisfied(),即策略恒被满足,不产生任何额外约束。

必需处理器的来源:注册服务定义

既然必需处理器"按注册服务定义",就需要在服务注册表(service registry)中为对应应用声明。以 JSON 服务定义为例如下(详见 Configuring-Service-AuthN-Policy.md):

{ "@class" : "org.apereo.cas.services.CasRegisteredService", "serviceId" : "https://app.example.org/.+", "name" : "ExampleApp", "id" : 1, "authenticationPolicy" : { "@class" : "org.apereo.cas.services.DefaultRegisteredServiceAuthenticationPolicy", "requiredAuthenticationHandlers" : ["java.util.TreeSet", [ "AuthNHandlerName" ]], "excludedAuthenticationHandlers" : ["java.util.TreeSet", [ ]] } }

对应的实现类是 DefaultRegisteredServiceAuthenticationPolicy.java,其中:

  • requiredAuthenticationHandlers:一组必需的认证处理器标识/名称,强制该服务只接受携带这些名称的认证策略,未出现的处理器即使成功也不算数;
  • excludedAuthenticationHandlers:一组被排除的认证处理器名称,用于"拉黑"某些处理器。

注意:CAS 中每个认证方法都有默认名称,且绝大多数方法可通过 CAS 设置项为其指定自定义名称,因此requiredAuthenticationHandlers中填写的名称必须与运行时实际注册的处理器名称精确匹配。

这一"全局开关 + 服务级名单"的组合非常适合 MFA(多因素认证)场景:例如只允许"密码 + 一次性口令(OTP)"同时成功的高安全服务,可以把两个处理器都列入requiredAuthenticationHandlers

测试用例佐证

仓库中的集成测试 MultifactorAuthenticationTests.java 通过@TestPropertySource同时启用了cas.authn.policy.required-handler-authentication-policy-enabled=truecas.authn.policy.any.try-all=true,并验证了如下行为:

  • 使用单一密码凭证访问"普通安全"服务:放行(verifyAllowsAccessToNormalSecurityServiceWithPassword);
  • 使用单一密码凭证访问"高安全"服务:抛出UnsatisfiedAuthenticationPolicyExceptionverifyDeniesAccessToHighSecurityServiceWithPassword);
  • 同时提供密码与 OTP 两种凭证访问"高安全"服务:放行,且断言authn.getSuccesses()同时包含AcceptUsersAuthenticationHandlerTestOneTimePasswordAuthenticationHandler两个处理器名称(verifyAllowsAccessToHighSecurityServiceWithPasswordAndOTPViaRenew)。

这个测试恰好演示了全局必需处理器策略与多凭证认证(multi-credential)配合的完整链路:策略要求所有必需处理器都成功,而"高安全"服务需要两个处理器全部命中。

自定义全局认证策略

除了内置开关,CAS 允许完全自定义全局认证策略——定义你自己的globalAuthenticationPolicyBean 即可覆盖默认行为。文档给出了最小示例:

@AutoConfiguration public class MyConfiguration { @Bean public AuthenticationPolicy globalAuthenticationPolicy() { return new MyAuthenticationPolicy(); } }

为了让这个 Bean 真正生效,需要理解AuthenticationPolicy接口的契约。该接口定义于 AuthenticationPolicy.java,核心方法是:

@FunctionalInterface public interface AuthenticationPolicy extends Ordered, Serializable, NamedObject { AuthenticationPolicyExecutionResult isSatisfiedBy(@Nullable Authentication authentication, Set<AuthenticationHandler> authenticationHandlers, ConfigurableApplicationContext applicationContext, Map<String, ? extends Serializable> context) throws Throwable; default boolean shouldResumeOnFailure(final Throwable failure) { return failure != null; } }

自定义实现时需要关注:

  • isSatisfiedBy:判定方法,接收当前Authentication、被选中的认证处理器集合、应用上下文与策略上下文(含RegisteredServiceService),返回AuthenticationPolicyExecutionResult(通过success()/failure()构造,AuthenticationPolicyExecutionResult.java);
  • 接口还提供了两个便捷工厂方法:AuthenticationPolicy.alwaysSatisfied()(恒满足)与AuthenticationPolicy.neverSatisfied()(恒不满足),可作为自定义策略的起点或占位实现;
  • shouldResumeOnFailure决定认证链在失败时是否继续执行,默认在failure != null时恢复执行;
  • getOrder()默认返回Ordered.LOWEST_PRECEDENCE,当存在多个策略时可据此控制判定顺序。

如何注册自定义配置到 CAS 运行时

@AutoConfiguration类需要被 Spring Boot 的自动配置机制发现。按 Configuration-Management-Extensions.md 的指引:

  1. 在项目的src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中登记你的配置类全限定名:
org.apereo.cas.custom.config.MyConfiguration
  1. 如需让 Bean 支持配置热刷新,可加@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)注解,使外部属性变更触发上下文刷新时自动重载;
  2. 可通过@Order(...)控制多个配置类的加载顺序。

覆盖默认 Bean 的要点

CAS 的默认globalAuthenticationPolicy使用了@ConditionalOnMissingBean(name = "globalAuthenticationPolicy")(见 CasCoreConfiguration.java)。这意味着:只要你的自定义 Bean 使用相同的名称globalAuthenticationPolicy,Spring 就会优先使用你的实现,而跳过 CAS 内置定义。这正是"用同名 Bean 覆盖内置策略"的标准做法,无需改动 CAS 源码。

如果你的自定义策略需要感知当前注册服务,可以从isSatisfiedBycontext参数中取出RegisteredService.class.getName()键对应的对象,与内置实现(CasCoreConfiguration.java)的做法保持一致。

全局策略与策略族的其他成员

cas.authn.policy命名空间下还提供了多种全局可用的策略族成员(AuthenticationPolicyProperties.java),它们与"必需处理器全局策略"共同构成完整的策略矩阵:

配置属性语义
cas.authn.policy.any.*任一认证处理器成功即满足(try-all选项可避免短路、尝试全部处理器)
cas.authn.policy.req.*指定名称的处理器必须成功验证其凭证
cas.authn.policy.all.*所有给定凭证都认证成功才满足(典型用于多因素场景)
cas.authn.policy.all-handlers.*所有给定认证处理器都成功才满足
cas.authn.policy.not-prevented.*认证事件未被PreventedException阻断即满足
cas.authn.policy.unique-principal.*同一主体不允许重复登录(需查询票据注册表)
cas.authn.policy.required-attributes.*认证结果必须包含指定属性才满足
cas.authn.policy.groovy.*/cas.authn.policy.rest.*通过 Groovy 脚本或 REST 端点判定策略

其中req对应的配置模型是 RequiredAuthenticationHandlerAuthenticationPolicyProperties.java,包含handlerName(必需处理器名称)与tryAll(凭证数量须等于成功与失败之和)两个字段。各类策略的完整说明可参考 Configuring-Authentication-Policy.md,而服务级策略如何映射到这些全局类型(Allowed / Excluded / Any / All / Not Prevented / Groovy / REST)见 Configuring-Service-AuthN-Policy.md。

小结

全局认证策略是 CAS 票据生命周期中一道"总闸门":它由globalAuthenticationPolicyBean 承载,在票据发行与验证时通过AbstractCentralAuthenticationService.getAuthenticationSatisfiedByPolicy被调用,失败时以UnsatisfiedAuthenticationPolicyException阻止流程。实际落地时有两条路径:

  1. 零代码:开启cas.authn.policy.required-handler-authentication-policy-enabled=true,并在服务定义中通过requiredAuthenticationHandlers声明必需处理器,即可实现"指定认证方式必须全部成功"的强制约束;
  2. 完全自定义:实现AuthenticationPolicy接口,以globalAuthenticationPolicy同名 Bean 注册到@AutoConfiguration配置类中,从而覆盖内置策略,实现任意自定义的判定逻辑。

结合仓库中的集成测试 MultifactorAuthenticationTests.java,可以快速验证策略行为是否符合预期,为多因素认证、高安全服务访问控制等场景提供可复现的参考模板。

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

【免费下载链接】cas

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

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

相关推荐

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

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

最近体验了这款免费云服务器,简单分享下使用感受。

服务器地址我就不说了&#xff0c;当前状态正常运行&#xff0c;支持 VNC 连接&#xff0c;可以自主重启、开关机&#xff0c;也支持重装系统、快照备份&#xff0c;基础功能很齐全。免费机型采用发帖延期机制&#xff0c;每次审核通过可以延长 5 天使用时间。需要在指定平台发…

作者头像 李华
网站建设 2026/9/24 9:45:11

国产操作系统下语音推理服务的依赖排查

一、问题背景语音模型在开发环境能够运行&#xff0c;并不代表复制到目标设备后也能正常启动。在国产操作系统环境中&#xff0c;问题可能来自动态库、处理器架构、推理框架、驱动或音频设备依赖。排查时应避免一上来就重装系统或更换模型&#xff0c;先收集环境信息&#xff0…

作者头像 李华
网站建设 2026/9/24 9:39:23

CADe SIMU电气控制仿真入门:原理图设计与动态验证

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

作者头像 李华
网站建设 2026/9/24 9:38:38

Obsidian 从入门到实践:用 Markdown 与双向链接构建个人知识库

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

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

华为U2000网管验收全流程与避坑指南

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

作者头像 李华
网站建设 2026/9/24 9:33:36

示波器实测DC-DC纹波与噪声:正确的探头接地方法

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

作者头像 李华