Apereo CAS Not Prevented 认证策略:原理、配置与 fail-closed 实战
2026/9/24 1:52:16 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

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

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

导读

在 Apereo CAS 的多种认证策略(Authentication Policy)中,Not Prevented策略提供了一种“在不确定的安全事件面前宁可失败”的判定语义:仅当认证事件没有被PreventedException阻塞时才判定为满足。本文以官方文档 Configuring-Authentication-Policy-NotPrevented.md 为核心骨架,结合源码实现、配置属性模型与单元测试,深入讲解该策略的适用场景、配置方式、底层判定逻辑,以及它与“至少一个凭证通过”策略的继承关系,帮助你准确决定何时启用它。

一、策略语义:什么时候算“满足”

官方文档对该策略的定义只有一句话,但语义非常精确:

Satisfied if and only if the authentication event is not blocked by aPreventedException.

即:当且仅当认证事件未被PreventedException阻塞时,该策略才被满足。这里的“阻塞”并不等于“认证失败”——它是认证基础设施层面的一种特殊错误状态。

PreventedException是什么

PreventedException定义在 api/cas-server-core-api-authentication/src/main/java/org/apereo/cas/authentication/PreventedException.java,源码注释明确说明:

Describes an error condition where authentication was prevented for some reason, e.g. communication error with back-end authentication store.

也就是说,它描述的是“认证因某种原因被阻止”的错误条件,典型的例子是与后端认证存储(如 LDAP、数据库)发生通信错误。这类错误不同于“用户名或密码错误”——后者是确定性的业务失败,而前者属于不确定性的系统级异常。它携带固定错误码:

public static final String CODE = "BLOCKED_AUTHN_REQUEST";

当认证处理器在无法确定用户凭据是否有效(例如后端不可达)时抛出PreventedException,CAS 会将其记录到认证事件的失败列表(Authentication.getFailures())中。

一句话概括

Not Prevented策略 = “至少有一个凭证认证成功”“没有出现PreventedException阻塞”。只要出现任何被阻止的认证尝试,无论其他凭据是否成功,策略整体判定为不满足。

二、源码实现:NotPreventedAuthenticationPolicy

策略的完整实现位于 core/cas-server-core-authentication-api/src/main/java/org/apereo/cas/authentication/policy/NotPreventedAuthenticationPolicy.java。

继承关系

public class NotPreventedAuthenticationPolicy extends AtLeastOneCredentialValidatedAuthenticationPolicy

它直接继承自AtLeastOneCredentialValidatedAuthenticationPolicy(实现见 AtLeastOneCredentialValidatedAuthenticationPolicy.java),并在构造函数中强制传入tryAll = true

public NotPreventedAuthenticationPolicy() { super(true); }

这一点非常关键:父类中tryAll标志为true时,策略要求所有有资格处理本次认证事务的处理器(handler)都完成一次成功的认证,而不仅仅是“至少一个成功”。因此Not Prevented实际上是比AtLeastOneCredentialValidated更严格的一种“fail-closed(故障关闭)”变体——源码注释对此有明确说明:

This policy may be a desirable alternative toAtLeastOneCredentialValidatedAuthenticationPolicyfor cases where deployers wish to fail closed for indeterminate security events.

核心判定逻辑

isSatisfiedBy方法分两步判定:

if (authentication == null) { LOGGER.warn("Authentication attempt is null and cannot satisfy policy"); return AuthenticationPolicyExecutionResult.failure(); } val fail = authentication.getFailures() .values() .stream() .anyMatch(failure -> failure.getClass().isAssignableFrom(PreventedException.class)); if (fail) { LOGGER.warn("Authentication policy has failed given at least one authentication failure is found to prevent authentication"); return AuthenticationPolicyExecutionResult.failure(); } return super.isSatisfiedBy(authentication, authenticationHandlers, applicationContext, context);

逻辑顺序为:

  1. 空认证直接失败:认证事件为null时记录 WARN 并返回失败;
  2. 扫描失败列表:遍历authentication.getFailures(),只要存在一个PreventedException(或其子类,通过isAssignableFrom判断),立即判定策略失败;
  3. 委托父类:没有阻止性异常时,交给父类的AtLeastOneCredentialValidated逻辑(此时tryAll=true),要求所有 handler 均有成功记录,且成功列表非空。

从源码结构可以推断,正是第 2 步的“一票否决”式检查,实现了文档所述的 “not blocked by aPreventedException” 语义:哪怕有一个凭据因系统错误被阻止,整个策略也不满足。

单元测试验证

策略行为在 NotPreventedAuthenticationPolicyTests.java 中有两个反向验证用例(标记@Tag("AuthenticationPolicy")):

  • verifyOperationPrevented:构建一个带PreventedException失败的认证事件,断言isSatisfiedBy返回失败;
  • verifyOperationNotPrevented:构建一个无成功记录、也无被阻止异常的认证事件,断言策略仍然返回失败(因为tryAll=true下没有任何成功事务)。

两个用例共同印证:该策略既拒绝“被阻止”的认证,也拒绝“没有成功”的认证,是双重收紧的 fail-closed 语义。

三、全局启用:cas.authn.policy.not-prevented配置

策略的全局配置属性集中在cas.authn.policy.not-prevented命名空间下(官方文档通过{% include_cached casproperties.html properties="cas.authn.policy.not-prevented" %}自动渲染完整属性表)。

属性模型定义在 NotPreventedAuthenticationPolicyProperties.java,其本身没有额外字段,全部继承自 BaseAuthenticationPolicyProperties.java:

属性类型默认值说明
cas.authn.policy.not-prevented.enabledbooleanfalse是否启用该策略。只有设为true时该策略才会被装配
cas.authn.policy.not-prevented.nameString认证策略名称,便于日志与审计识别
cas.authn.policy.not-prevented.orderintOrdered.LOWEST_PRECEDENCE策略在多策略组合中的执行顺序,数值越小优先级越高

典型配置示例(application.yml):

cas: authn: policy: not-prevented: enabled: true name: NotPreventedPolicy order: 10

属性如何被装配为策略对象

cas.authn.policy.not-prevented在配置模型中挂载于 AuthenticationPolicyProperties.java 的notPrevented字段:

private NotPreventedAuthenticationPolicyProperties notPrevented = new NotPreventedAuthenticationPolicyProperties();

装配逻辑位于 CoreAuthenticationUtils.java 的buildAuthenticationPolicyMap系列方法中:

if (policyProps.getNotPrevented().isEnabled()) { val policy = new NotPreventedAuthenticationPolicy(); return CollectionUtils.wrapList(configureAuthenticationPolicy(policy, policyProps.getNotPrevented())); }

可以看到:只有enabled=true时才会实例化NotPreventedAuthenticationPolicy并加入策略集合;configureAuthenticationPolicy负责把nameorder等元数据同步到策略对象。配置模型类标注了@RequiresModule(name = "cas-server-core-authentication", automated = true),表明该功能由核心认证模块提供,开箱即用,无需额外引入 support 模块。

CoreAuthenticationUtilsTests中也验证了该属性开关的行为(见 CoreAuthenticationUtilsTests.java):setEnabled(false)setEnabled(true)分别对应策略的不装配与装配。

四、服务级粒度:Registered Service 认证策略条件

除了全局配置,Not Prevented还可以作为单个已注册服务(Registered Service)的认证策略条件使用,实现按应用定制的认证要求。这通过NotPreventedRegisteredServiceAuthenticationPolicyCriteria完成,实现见 NotPreventedRegisteredServiceAuthenticationPolicyCriteria.java:

public class NotPreventedRegisteredServiceAuthenticationPolicyCriteria implements RegisteredServiceAuthenticationPolicyCriteria { @Override public AuthenticationPolicy toAuthenticationPolicy(final RegisteredService registeredService) { return new NotPreventedAuthenticationPolicy(); } }

从源码结构看,该 Criteria 直接把服务级条件转换为NotPreventedAuthenticationPolicy实例,因此服务级使用与全局启用共享同一套判定逻辑。相关服务解析逻辑在 RegisteredServiceAuthenticationPolicyResolverTests.java 中有测试覆盖。

在服务注册 JSON 中,可以通过authenticationPolicy.criteria类名方式引用该条件(具体序列化形态取决于服务注册文件的@class与 criteria 类型)。其可被 Jackson 反序列化,并由RegisteredServiceAuthenticationPolicyResolver在服务认证时解析为实际策略。

五、与相近策略的对比与选型建议

在 CAS 的认证策略体系中,Not Prevented与以下几个策略容易混淆,理解差异有助于正确选型:

策略核心语义失败关闭程度
AllAuthenticationHandlersSucceeded所有参与认证的 handler 都必须成功
NotPrevented不得出现PreventedException阻塞,且所有 handler 均成功(tryAll=true高(含系统异常关闭)
AtLeastOneCredentialValidated至少一个凭证通过验证即可(默认tryAll=false

从源码继承关系看,NotPrevented的关键价值在于把“系统级不确定错误(如后端认证存储通信失败)”纳入了策略判定:在多后端认证场景下,如果 LDAP 短暂不可达导致PreventedException,而密码本身可能完全正确,此时若使用宽松的“至少一个成功”策略,请求可能仍然放行;而NotPrevented会果断失败,避免在基础设施异常时做出错误的信任决策。

适用场景建议

  • 安全敏感、要求 fail-closed 的应用(如金融、政务系统)——后端异常宁可拒绝也不放行;
  • 多认证后端并存、需要显式控制“部分后端故障”影响的部署;
  • 服务级差异化:对个别高安全要求应用启用NotPrevented,其他应用保持默认策略。

需要留意的代价:由于tryAll=true,该策略要求所有有资格处理的 handler 都成功,因此对后端可用性要求更高;任何单一后端故障都会导致整体认证失败,业务可用性会相应降低。启用前应评估后端高可用保障。

六、常见问题排查

  1. 配置了not-prevented.enabled: true但策略未生效:检查属性路径是否为cas.authn.policy.not-prevented(注意是not-prevented连字符形式);同时确认该开关对应的装配分支在 CoreAuthenticationUtils.java 中按顺序生效,多个策略同时启用时会按order执行。
  2. 认证日志出现Authentication policy has failed given at least one authentication failure is found to prevent authentication:表示存在PreventedException,应优先排查后端认证存储(LDAP、JDBC 等)的连通性与超时配置,而不是调整策略。
  3. 策略名称/顺序在审计中不清晰:通过nameorder属性为策略命名并排序,便于多策略组合时的日志定位。

小结

Not Prevented是 Apereo CAS 中面向“不确定安全事件”的 fail-closed 认证策略:它在“所有认证处理器均成功”的基础上,额外要求认证事件中不存在PreventedException阻塞。通过 NotPreventedAuthenticationPolicy.java 的失败扫描逻辑,以及cas.authn.policy.not-prevented配置与服务级 Criteria 两条装配路径,你可以灵活地在全局或单服务粒度启用这一策略,为高安全场景提供更严格、更可预期的认证判定。

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

【免费下载链接】cas

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

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

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

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

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

立即咨询