☰
Apereo CAS 自定义服务访问策略执行器(Custom Service Access Strategy Enforcer)实现指南
2026/10/16 2:31:50 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 单点登录
  • 身份认证

【免费下载链接】cas

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

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

在 Apereo CAS 中,服务的访问授权(Service Access Strategy)决定了一个已注册的应用服务能否参与 CAS 认证/SSO 流程。本篇指南聚焦于 CAS 预留的自定义扩展点——RegisteredServiceAccessStrategyEnforcer:通过实现该接口并注册为 Spring Bean,你可以完全接管或补充 CAS 的服务授权判定逻辑,把自定义的授权规则(如对接内部风控系统、调用业务侧决策 API 等)无缝嵌入 CAS 运行时。读完本文,你将掌握自定义执行器的接口契约、注册方式、调用链原理,以及如何与 CAS 自带的 Groovy 执行器协同工作。

一、核心概念:什么是 RegisteredServiceAccessStrategyEnforcer

在 CAS 中,服务访问授权由两类组件协作完成:

  1. 访问策略(Access Strategy):与具体服务定义(RegisteredService)绑定,声明式地描述"该服务在什么条件下允许访问",例如必须携带哪些属性、启用/禁用、是否允许 SSO 等,典型实现如DefaultRegisteredServiceAccessStrategy。
  2. 访问策略执行器(Access Strategy Enforcer):在认证与票据签发等关键节点被调用的执行入口,负责在运行时把访问策略真正"执行"起来,并产出可审计的结果。

本指南的主角是后者。RegisteredServiceAccessStrategyEnforcer是 CAS 从7.0.0起提供的扩展接口,其定义位于 RegisteredServiceAccessStrategyEnforcer.java:

@FunctionalInterface public interface RegisteredServiceAccessStrategyEnforcer extends AuditableExecution { }

接口本身非常精简:它是一个标注了@FunctionalInterface的空接口,继承自AuditableExecution。真正需要实现的只有一个方法——来自AuditableExecution的:

AuditableExecutionResult execute(AuditableContext context) throws Throwable;

AuditableExecution接口定义于 AuditableExecution.java,其 javadoc 明确指出:它是"CAS 服务器及其各模块在诸多审计点上的审计轨迹执行抽象",实现者通常还会使用 Inspektr 审计库的@Audit注解捕获审计数据。

二、注册自定义执行器(原文档核心步骤)

原文档给出的注册方式非常直接:编写一个@AutoConfiguration配置类,声明一个返回RegisteredServiceAccessStrategyEnforcer的@Bean,CAS 会在运行时自动拾取并用于授权执行。完整示例:

package org.apereo.cas; @AutoConfiguration @EnableConfigurationProperties(CasConfigurationProperties.class) public class MyConfiguration { @Bean @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) public RegisteredServiceAccessStrategyEnforcer myEnforcer() { return new MyRegisteredServiceAccessStrategyEnforcer(); } }

其中需要你自行实现MyRegisteredServiceAccessStrategyEnforcer,例如:

public class MyRegisteredServiceAccessStrategyEnforcer implements RegisteredServiceAccessStrategyEnforcer { @Override public AuditableExecutionResult execute(final AuditableContext context) throws Throwable { // 读取上下文中的服务、认证、票据等信息,执行自定义授权判定 final AuditableExecutionResult result = AuditableExecutionResult.builder() .registeredService(context.getRegisteredService().orElse(null)) .service(context.getService().orElse(null)) .build(); if (/* 你的授权规则不通过 */) { result.setException(UnauthorizedServiceException.denied("Custom rule denied access")); } return result; } }

注意@Bean方法上的@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT):它使该 Bean 具备热刷新能力,当外部属性变化触发 CAS 上下文刷新时,执行器实例可被自动重建。

让 CAS 识别你的 AutoConfiguration

@AutoConfiguration类需要被 Spring Boot 的自动装配机制发现。参照 Configuration-Management-Extensions.md 的说明,在 CAS Overlay 的src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中追加一行:

org.apereo.cas.MyConfiguration

用 @Conditional 覆盖 CAS 自带执行器

CAS 自身也在 CasCoreServicesConfiguration.java 中注册了执行器相关 Bean,且大多带有@ConditionalOnMissingBean(name = ...)守卫:

@ConditionalOnMissingBean(name = AuditableExecution.AUDITABLE_EXECUTION_REGISTERED_SERVICE_ACCESS) @Bean @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) public AuditableExecution registeredServiceAccessStrategyEnforcer(...) { return new RegisteredServiceAccessStrategyAuditableEnforcer(applicationContext, principalAccessStrategyEnforcer); }

AuditableExecution.AUDITABLE_EXECUTION_REGISTERED_SERVICE_ACCESS的值为"registeredServiceAccessStrategyEnforcer"。这意味着:只要你自定义的 Bean 使用相同的 Bean 名称,即可整体替换 CAS 默认的执行器。若你只想"追加"一层自定义判定而不替换默认实现,则直接按上文声明一个新命名的 Bean 即可——下文会说明两者在调用链上的差异。

三、执行链原理:自定义执行器如何被调用

CAS 默认执行器是RegisteredServiceAccessStrategyAuditableEnforcer(见 RegisteredServiceAccessStrategyAuditableEnforcer.java)。它的execute方法按优先级依次尝试多种判定路径:

@Override @Audit(action = AuditableActions.SERVICE_ACCESS_ENFORCEMENT, actionResolverName = AuditActionResolvers.SERVICE_ACCESS_ENFORCEMENT_ACTION_RESOLVER, resourceResolverName = AuditResourceResolvers.SERVICE_ACCESS_ENFORCEMENT_RESOURCE_RESOLVER) public AuditableExecutionResult execute(final AuditableContext context) { return byExternalAccessStrategyEnforcers(context) .or(() -> byServiceTicketAndAuthnResultAndRegisteredService(context)) .or(() -> byServiceAndRegisteredServiceAndTicketGrantingTicket(context)) .or(() -> byServiceAndRegisteredServiceAndPrincipal(context)) .or(() -> byServiceAndRegisteredServiceAndAuthentication(context)) .or(() -> byServiceAndRegisteredService(context)) .or(() -> byRegisteredService(context)) .orElseGet(() -> { /* ...默认拒绝... */ }); }

关键的一步是byExternalAccessStrategyEnforcers(context)——它从 Spring 上下文中取出所有RegisteredServiceAccessStrategyEnforcer类型的 Bean,按顺序执行并收集第一个"执行失败"的结果:

protected Optional<AuditableExecutionResult> byExternalAccessStrategyEnforcers(final AuditableContext context) { return accessStrategyEnforcers .orderedStream() .filter(BeanSupplier::isNotProxy) .map(Unchecked.function(enforcer -> enforcer.execute(context))) .filter(Objects::nonNull) .filter(AuditableExecutionResult::isExecutionFailure) .findFirst(); }

由此可以得出两个重要结论:

  • 追加型注册(推荐):你新增的执行器 Bean 会被byExternalAccessStrategyEnforcers一并收集执行。只要你的执行器返回带异常的结果(isExecutionFailure() == true),授权即被拒绝;若返回无异常的结果,CAS 会继续走内置的默认判定路径。
  • 替换型注册:若用同名 Bean 覆盖registeredServiceAccessStrategyEnforcer,你的执行器将完全替代上述整套判定逻辑,此时你需要自行处理各类上下文分支。

而执行器的调用入口位于核心认证服务 DefaultCentralAuthenticationService.java:

protected void enforceRegisteredServiceAccess(final AuditableContext audit) throws Throwable { val accessResult = configurationContext.getRegisteredServiceAccessStrategyEnforcer().execute(audit); accessResult.throwExceptionIfNeeded(); }

也就是说,在 CAS 认证 / 授权 / 验证的关键流程中,一旦上下文被构造为AuditableContext(携带service、registeredService、principal等),就会触发执行器;若结果携带异常,则通过throwExceptionIfNeeded()抛出(如UnauthorizedServiceException),最终阻断请求。

上下文与结果对象

  • AuditableContext(AuditableContext.java):执行器收到的输入,通过@SuperBuilder构建,可包含service、registeredService、principal、authentication、serviceTicket、authenticationResult、ticketGrantingTicket、httpRequest、httpResponse以及扩展属性properties。这些字段均通过Optional暴露,你的自定义执行器应做好空值判断。
  • AuditableExecutionResult(AuditableExecutionResult.java):执行器返回的结果,通过setException(...)标记失败,isExecutionFailure()判断是否失败,throwExceptionIfNeeded()抛出异常。此外还可通过addProperty(...)向结果追加自定义属性,供审计消费。

四、源码佐证:CAS 自带的 Groovy 执行器实现

CAS 仓库中提供了一个开箱即用的RegisteredServiceAccessStrategyEnforcer实现——GroovyRegisteredServiceAccessStrategyEnforcer(GroovyRegisteredServiceAccessStrategyEnforcer.java)。它可以作为你编写自定义执行器的绝佳参照:

public class GroovyRegisteredServiceAccessStrategyEnforcer implements RegisteredServiceAccessStrategyEnforcer, DisposableBean { private final ExecutableCompiledScript script; @Override public @Nullable AuditableExecutionResult execute(final AuditableContext context) throws Throwable { val args = new Object[]{context, LOGGER}; return script.execute(args, AuditableExecutionResult.class, true); } @Override public void destroy() { script.close(); } }

它在 CasCoreServicesConfiguration.java 中通过条件 Bean 注册,配置项为cas.access-strategy.groovy.location:

@ConditionalOnMissingBean(name = "groovyRegisteredServiceAccessStrategyEnforcer") @Bean @RefreshScope(proxyMode = ScopedProxyMode.DEFAULT) @ConditionalOnMissingGraalVMNativeImage public RegisteredServiceAccessStrategyEnforcer groovyRegisteredServiceAccessStrategyEnforcer(...) { return BeanSupplier.of(RegisteredServiceAccessStrategyEnforcer.class) .when(BeanCondition.on("cas.access-strategy.groovy.location").exists() .given(applicationContext.getEnvironment())) .supply(() -> { val location = casProperties.getAccessStrategy().getGroovy().getLocation(); val scriptFactory = ExecutableCompiledScriptFactory.getExecutableCompiledScriptFactory(); return new GroovyRegisteredServiceAccessStrategyEnforcer(scriptFactory.fromResource(location)); }) .otherwiseProxy() .get(); }

对应配置属性模型定义于 AccessStrategyProperties.java,即cas.access-strategy.groovy.location指向一个 Groovy 脚本资源。该脚本与自定义执行器走的是同一条调用链:脚本接收AuditableContext与LOGGER两个参数,返回AuditableExecutionResult。

仓库测试资源 ServiceAccessStrategy.groovy 给出了脚本的完整写法:

import org.apereo.cas.audit.* import org.apereo.cas.services.* def run(Object[] args) { def context = args[0] as AuditableContext def logger = args[1] logger.info("Checking access for ${context.registeredService}") if (context.service.isPresent()) { def service = context.service.get() logger.info("Service request is ${service.id}") if (service.id.startsWith("https://")) { def result = AuditableExecutionResult.builder().build() result.setException(UnauthorizedServiceException.denied("Service Unauthorized")) logger.error("Service ${service.id} is unauthorized by Groovy script") return result } } return AuditableExecutionResult.builder().build() }

对应的测试类 GroovyRegisteredServiceAccessStrategyEnforcerTests.java 验证了"执行器返回失败结果"的完整链路:测试以cas.access-strategy.groovy.location=classpath:ServiceAccessStrategy.groovy启动上下文,注入registeredServiceAccessStrategyEnforcer与groovyRegisteredServiceAccessStrategyEnforcer,构造包含registeredService、service、authentication的AuditableContext后执行,并断言results.isExecutionFailure()为真。这同时印证了:自定义执行器只需在execute中返回带异常的结果,即可实现授权拒绝。

五、与 Groovy 全局策略的关系与选型建议

需要区分两个层面的 Groovy 扩展:

  • 全局执行器层:即上文GroovyRegisteredServiceAccessStrategyEnforcer(配置项cas.access-strategy.groovy.location),对所有服务的授权执行生效,属于"执行器"层面的策略。
  • 服务级策略层:即GroovyRegisteredServiceAccessStrategy(见 Service-Access-Strategy-Groovy.md),挂在单个服务定义 JSON 的accessStrategy字段上,属于"访问策略"层面的声明。

两者叠加使用时,判定顺序为:先执行全局执行器(含你的自定义执行器),再落入默认执行器的内置策略判定。若你的场景需要全站统一、强定制的授权逻辑(如调用外部决策引擎),自定义RegisteredServiceAccessStrategyEnforcer是正确选择;若只需为个别服务声明规则,优先使用服务级策略或 Groovy 脚本策略。

六、完整落地步骤小结

  1. 实现接口:在 CAS Overlay 工程中创建MyRegisteredServiceAccessStrategyEnforcer implements RegisteredServiceAccessStrategyEnforcer,在execute(AuditableContext)中编写授权逻辑,返回AuditableExecutionResult(拒绝时setException(UnauthorizedServiceException.denied(...)))。
  2. 声明 Bean:编写@AutoConfiguration+@EnableConfigurationProperties(CasConfigurationProperties.class)配置类,以@Bean+@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)暴露执行器;若需替换默认执行器,Bean 名称须为registeredServiceAccessStrategyEnforcer。
  3. 注册装配:将配置类全限定名写入META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports。
  4. 验证:参照 GroovyRegisteredServiceAccessStrategyEnforcerTests.java 的写法编写集成测试,构造AuditableContext并断言执行结果;通过 CAS 审计日志(SERVICE_ACCESS_ENFORCEMENT)确认执行器被调用。
  5. 上线观察:利用@RefreshScope的能力,在属性变更触发上下文刷新时热更新执行器配置,避免频繁重启。

至此,你已经掌握了 CAS 自定义服务访问策略执行器的完整实现路径:从接口契约、Bean 注册、调用链原理到测试验证,均可在当前仓库中找到对应的源码与测试佐证,可据此把任意业务授权规则安全、可控地接入 CAS 的认证授权流程。

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

【免费下载链接】cas

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

项目地址:https://gitcode.com/gh_mirrors/ca/cas
点击查看免费下载
上一篇:openeuler/distributed-beget最佳实践:10个提升参数管理效率的技巧
下一篇:Piko 登录令牌导入/导出补丁(Import/Export login token)完整指南

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

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

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

立即咨询