☰
Apereo CAS Simple MFA 通信策略全解析:通过邮件、短信、语音电话与平台通知投递一次性令牌
2026/10/15 17:03:45 网站建设 项目流程
  • 后端
  • 认证鉴权
  • 单点登录

【免费下载链接】cas

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

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

导读

Apereo CAS 的 Simple Multifactor Authentication(简单多因素认证,以下简称 Simple MFA)是一种基于一次性令牌(one-time token)的轻量 MFA 方案:系统生成令牌后,需要通过合适的通信渠道把它送到最终用户手中。本文以 Simple Multifactor Authentication - Communication Strategy 文档为骨架,完整梳理 CAS 在令牌投递阶段支持的四种通信策略——邮件(Email)、短信(SMS)、语音电话(Phone Call)与平台通知(Notification),并给出基于当前仓库源码与配置模型的完整参数说明、默认值、底层执行流程与故障处理路径。读完本文,你将能够独立完成 Simple MFA 的通信渠道选型、属性解析配置、邮箱注册流程启用以及限流与令牌策略调优。

一、Simple MFA 令牌投递:一次认证、多路送达

Simple MFA 的核心交互过程是:用户在完成首次身份认证后,CAS 为其生成一个短时效的一次性令牌,随后将令牌通过一种或多种通信渠道发送给用户;用户把收到的令牌输入到认证页面完成二次验证。整个环节中,"令牌如何送达用户"完全由通信策略(Communication Strategy)决定。

在 CasSimpleMultifactorTokenCommunicationStrategy.java 中,CAS 用枚举TokenSharingStrategyOptions定义了四种可用的投递选项:

策略选项说明对应配置前缀
SMS通过短信渠道发送令牌cas.authn.mfa.simple.sms.*
EMAIL通过邮件渠道发送令牌cas.authn.mfa.simple.mail.*
PHONE通过语音电话朗读令牌cas.authn.mfa.simple.phone.*
NOTIFICATION通过平台通知(如 Google Firebase、Slack、Apple APN)推送令牌cas.notifications.*

默认的通信策略实现all()会返回TokenSharingStrategyOptions.ALL,即四种渠道全部启用(最终能否真正发出取决于对应发送器是否已定义、收件人属性是否可解析)。

从源码结构看,令牌投递的入口是 CasSimpleMultifactorSendTokenAction.java 中的doExecuteInternal方法,其执行逻辑大致如下:

  1. 解析已认证的Principal,并getOrCreateToken获取(或重新生成)令牌;
  2. 通过tokenCommunicationStrategy.determineStrategy(token)确定本次采用的通信策略集合;
  3. 依次尝试tryToSendSms、tryToSendEmail,再根据策略集合判断是否发送平台通知(isNotificationSent)与拨打语音电话(CasSimpleMultifactorMakePhoneCall);
  4. 只要任一渠道投递成功,就把令牌写入票据注册表(storeToken)并进入成功状态;全部失败则路由到错误状态。

需要特别说明的是:收件人地址不是配置死的,而是从已认证 Principal 的属性中解析出来的。CAS 文档明确要求:"authenticated CAS principal is expected to carry enough attributes, configurable via CAS settings, in order for CAS to properly send text messages and/or email to the end-user",即 Principal 必须携带足够、且可通过 CAS 设置来指定的属性,CAS 才能据此找到手机号或邮箱。因此,配置通信渠道之前,必须先保证认证源的属性(Attribute)能被正确解析到 Principal 上。

二、邮件渠道:cas.authn.mfa.simple.mail.*

邮件是 Simple MFA 最常用的令牌投递渠道。其配置模型为 CasSimpleMultifactorAuthenticationEmailProperties.java,它继承自通用邮件属性 EmailProperties.java。可用属性如下:

属性默认值说明
cas.authn.mfa.simple.mail.attributeName["mail", "email"]指定从 Principal 属性中解析收件邮箱的属性名列表。可写多个属性名,每个属性名都会被逐一检查,可能触发发送多封邮件;支持 Spring 表达式语言(SpEL)解析
cas.authn.mfa.simple.mail.from无(必填)发件人地址
cas.authn.mfa.simple.mail.subject无(必填)邮件主题。可直接写死,也可使用#{subject-language-key}语法引用语言包中的消息键
cas.authn.mfa.simple.mail.text无邮件正文。可以是纯文本,也可以指向扩展名为.gtemplate的外部 Groovy 模板文件。纯文本中支持${token}、${tokenWithoutPrefix}等命名变量替换
cas.authn.mfa.simple.mail.cc空列表抄送地址
cas.authn.mfa.simple.mail.bcc空列表密送地址
cas.authn.mfa.simple.mail.replyTo空回复地址
cas.authn.mfa.simple.mail.htmlfalse正文是否按 HTML 渲染(依赖底层邮件实现,对默认 JavaMailSender 有效)
cas.authn.mfa.simple.mail.validateAddressesfalse是否校验收件地址格式(依赖底层实现)
cas.authn.mfa.simple.mail.priority1邮件X-Priority头,取值范围 1(最高)~ 5(最低)
cas.authn.mfa.simple.mail.acceptedEmailPattern无(默认关闭注册)正则表达式,控制用户在多因素流程中可注册的邮箱地址范围,例如^[a-zA-Z0-9._%+-]+@example\.org$。一旦配置,即开启"邮箱注册流程"(详见第四节)

其中text支持的两类模板机制值得展开:若正文是纯文本,内容会被EmailMessageBodyBuilder按命名变量做字符串替换;CasSimpleMultifactorSendEmail.java 的buildEmailBodyParameters会向模板注入两个关键变量:

  • token:完整的令牌票据 ID(形如CASMFA-...,前缀定义见 CasSimpleMultifactorAuthenticationTicket.java 中的PREFIX = "CASMFA");
  • tokenWithoutPrefix:去掉CASMFA-前缀后的纯令牌码,通常这才是发送给用户输入的验证码。

此外,Principal 的其余属性也会被CoreAuthenticationUtils.convertAttributeValuesToObjects转换后一并注入模板参数,因此邮件正文里可以直接引用用户属性。

一份完整的邮件渠道配置示例(YAML):

cas: authn: mfa: simple: mail: attributeName: ["mail", "email"] from: no-reply@example.org subject: Your CAS One-Time Token text: "Your token is ${tokenWithoutPrefix}. It expires shortly." cc: [] bcc: [] html: false priority: 1 # 可选:启用邮箱注册流程 acceptedEmailPattern: "^[a-zA-Z0-9._%+-]+@example\\.org$"

邮件最终发送动作由 CasSimpleMultifactorSendEmail.java 负责:它通过communicationsManager.email(emailRequest)触发底层邮件发送器,并根据cas.authn.mfa.simple.mail.attributeName中列出的每个属性构造EmailMessageRequest(支持多租户场景下的 tenant 提取与 locale 解析)。邮件底层提供商的完整接入方式(默认 JavaMail、Amazon SES、Mailgun、SendGrid 等)参见 Sending-Email-Configuration.md。

三、短信与语音电话渠道:cas.authn.mfa.simple.sms.* 与 cas.authn.mfa.simple.phone.*

3.1 短信(SMS)

短信渠道的配置模型是通用 SmsProperties.java,在 Simple MFA 下挂载于cas.authn.mfa.simple.sms.*:

属性默认值说明
cas.authn.mfa.simple.sms.text无(必填)短信正文。同样支持${token}、${tokenWithoutPrefix}变量替换;未配置时直接发送令牌码本身
cas.authn.mfa.simple.sms.from无(必填)短信发送方标识
cas.authn.mfa.simple.sms.attributeName["phone", "phoneNumber", "telephone", "telephoneNumber"]从 Principal 属性中解析收件手机号的属性名列表,支持 SpEL

短信的组装逻辑在 CasSimpleMultifactorSendSms.java 中:它遍历attributeName中的每个属性,从 Principal 解析手机号(去重后得到收件人列表),然后构造SmsRequest并调用communicationsManager.sms(smsRequest)。短信正文默认按smsProperties.getText()生成,若为空则直接使用token(即去掉前缀的纯令牌码)。

示例配置:

cas: authn: mfa: simple: sms: text: "Your CAS token is ${tokenWithoutPrefix}" from: CAS attributeName: ["phone", "phoneNumber"]

SMS 底层提供商接入方式(Twilio、Nexmo、Clickatell、SmsMode、TextMagic、Amazon SNS、Groovy 脚本、REST 等)参见 SMS-Messaging-Configuration.md。

3.2 语音电话(Phone Call)

从 7.0.0 开始,Simple MFA 还支持通过语音电话把令牌"读"给用户。配置模型为 PhoneProperties.java,挂载于cas.authn.mfa.simple.phone.*:

属性默认值说明
cas.authn.mfa.simple.phone.text无(必填)语音播报的文本内容,支持${token}、${tokenWithoutPrefix}变量替换
cas.authn.mfa.simple.phone.from无(必填)来电显示标识
cas.authn.mfa.simple.phone.attributeNamephone从 Principal 属性中解析电话号码的属性名(注意:这里是单个属性名,不是列表)

语音电话的拨号动作由 CasSimpleMultifactorMakePhoneCall.java 实现,它构造PhoneCallRequest并调用communicationsManager.phoneCall(callRequest);只有在communicationsManager.isPhoneOperatorDefined()为真时才真正执行。语音电话提供商的接入方式参见 Phone-Calls-Configuration.md。

四、平台通知渠道:Google Firebase、Slack 与 Apple APN

除短信与邮件外,令牌还可以通过平台通知(Notification)策略投递——这正是原文档中"Tokens may also be shared via notification strategies backed by platforms such as Google Firebase, etc."所指的能力。通知策略的触发条件有两个:

  1. 通信策略集合包含NOTIFICATION选项(默认全渠道策略即包含);
  2. 通知发送器已定义(communicationsManager.isNotificationSenderDefined())。

满足条件后,CasSimpleMultifactorSendTokenAction.java 的isNotificationSent方法会调用communicationsManager.notify(principal, "Apereo CAS Token", String.format("Token: %s", token.getId())),把完整令牌 ID 推送给用户。

通知相关的全局配置前缀为cas.notifications.*,CAS 支持的通知平台包括:

平台配置指南
Google Firebase Cloud Messaging(FCM)Notifications-Configuration-FCM.md
SlackNotifications-Configuration-Slack.md
Apple Push Notification(APN)Notifications-Configuration-APN.md
自定义实现Notifications-Configuration-Custom.md

注意:官方文档提示,部分通知提供商可能需要 CAS 的活跃/专业订阅(active/professional subscription)才能使用,详见 Notifications-Configuration.md。

五、令牌本身的策略配置:时效、长度与字符类型

令牌的"送达"只是链路的一半,令牌本身的生命周期与形态由cas.authn.mfa.simple.token.*控制。配置模型为 CasSimpleMultifactorAuthenticationTokenProperties.java,它进一步拆分为本地管理(core)与 REST 管理(rest)两套子配置。

5.1 本地令牌管理(core)

核心参数定义在 CoreCasSimpleMultifactorAuthenticationTokenProperties.java:

属性默认值说明
cas.authn.mfa.simple.token.core.timeToKillInSeconds30令牌在 CAS 服务器中的存活时间(秒)。这是令牌有效期,过期后用户输入的验证码将无法通过校验
cas.authn.mfa.simple.token.core.tokenLength6生成令牌的长度。注意:长度过短(小于 6 位字符或数字)在高并发下容易产生碰撞;CAS 会尝试重试生成唯一令牌,但若始终无法生成唯一值,最终会放弃
cas.authn.mfa.simple.token.core.tokenTypeNUMERIC令牌字符类型,可选NUMERIC(纯数字)、ALPHANUMERIC(字母数字混合)、ALPHABETIC(纯字母)

示例配置:

cas: authn: mfa: simple: token: core: timeToKillInSeconds: 60 tokenLength: 8 tokenType: NUMERIC

5.2 REST 令牌管理(rest)

cas.authn.mfa.simple.token.rest.*继承自 RestEndpointProperties(该文件位于api/cas-server-core-api-configuration-model模块),用于把令牌的生成/校验/存储委托给外部 REST 端点管理,适合需要与自有服务对接的场景。相关实现可参考 RestfulCasSimpleMultifactorAuthenticationService.java 及 RestfulCasSimpleMultifactorAuthenticationServiceTests.java。

六、源码级流程:从生成令牌到送达的完整调用链

6.1 发送动作的执行编排

令牌投递的主控动作 CasSimpleMultifactorSendTokenAction 内部编排了以下分支,理解它对排查"令牌收不到"问题至关重要:

  • 收件人为空时的兜底:当邮件和短信都未发出、且allRecipients为空时,若cas.authn.mfa.simple.mail.acceptedEmailPattern已配置(即邮箱注册已启用),则路由到register事件,引导用户先注册邮箱;否则进入错误状态;
  • 多收件人选择:当解析出的收件人超过 1 个(如 Principal 携带多个邮箱/手机号)时,会进入selectRecipientsView(simple-mfa/casSimpleMfaSelectRecipientsView),让用户选择本次要使用的收件地址。收件人地址在渲染前会用 SHA-512 哈希 + 脱敏处理(邮箱域名脱敏为****@****、手机号末四位脱敏为******),避免页面泄露完整联系方式;
  • 失败信息:短信失败会向页面注入cas.mfa.simple.label.contactfailed.sms消息,邮件失败注入cas.mfa.simple.label.contactfailed.email;全部渠道失败则记录错误日志并路由到unavailable结束状态。

6.2 Webflow 状态机

整个令牌发送、验证与邮箱注册的流程由 CasSimpleMultifactorWebflowConfigurer.java 构建,核心状态包括:

  • initLoginForm→ 成功后进入simpleMfaSendToken(发送令牌);
  • simpleMfaSendToken:成功 →viewLoginForm(输入令牌页面);失败 →unavailable;多收件人 →selectRecipientsView;需注册邮箱 →recipientsRegistrationView;
  • viewLoginForm:支持submit(提交令牌校验)与resend(重新发送令牌,即重新进入simpleMfaSendToken)两个跳转;
  • 邮箱注册子流程(仅在acceptedEmailPattern配置后启用):recipientsRegistrationView→simpleMfaVerifyEmail(校验邮箱归属)→updateEmailRegistrationView→updateEmailRegistration(CasSimpleMultifactorUpdateEmailAction.java,通过令牌携带待注册邮箱并更新账号属性)→resume回到发送令牌状态。

6.3 令牌的生成、存储与校验

令牌管理统一由 CasSimpleMultifactorAuthenticationService.java(接口)及其默认实现 DefaultCasSimpleMultifactorAuthenticationService.java 提供:

  • generate:通过CasSimpleMultifactorAuthenticationTicketFactory创建令牌票据(前缀CASMFA),并对"令牌 ID 已在票据注册表存在"的情况做最多 5 次重试(MAX_ATTEMPTS = 5),对应上述令牌长度过短导致碰撞的说明;
  • store:把令牌写入TicketRegistry(已存在则更新,否则新增);
  • validate:校验用户提交的验证码。值得注意的是,校验过程同样受 bucket4j 限流保护——Bucket4j 配置 挂载在cas.authn.mfa.simple.bucket4j.*(默认关闭,enabled=false),一旦触发限流会抛出FailedLoginException拒绝验证。

6.4 令牌码与票据 ID 的关系

需要澄清一个容易混淆的点:发送给用户的"验证码"与票据 ID 并不总是同一个值。CasSimpleMultifactorAuthenticationTicket.java 中的getCode方法表明:若票据属性中存在PROPERTY_CODE(code),则以该属性值作为用户验证码(用于无状态票据注册表等场景),否则退化为票据 ID 本身。同时,CasSimpleMultifactorSendTokenAction.storeToken会把实际存储后的票据 ID 写回CasSimpleMultifactorTokenCredential,确保用户输入的验证码始终与注册表中票据对应。

七、一个完整的最小配置示例

综合以上各节,一个同时启用邮件 + 短信 + 语音电话,并开启邮箱注册的 Simple MFA 通信配置如下:

cas: authn: mfa: simple: # 令牌策略 token: core: timeToKillInSeconds: 60 tokenLength: 6 tokenType: NUMERIC # 邮件渠道 mail: attributeName: ["mail", "email"] from: no-reply@example.org subject: "#{cas.mfa.simple.label.tokensubject}" text: "Your token is ${tokenWithoutPrefix}" acceptedEmailPattern: "^[a-zA-Z0-9._%+-]+@example\\.org$" # 短信渠道 sms: text: "Your CAS token is ${tokenWithoutPrefix}" from: CAS attributeName: ["phone", "phoneNumber"] # 语音电话渠道 phone: text: "Your CAS token is ${tokenWithoutPrefix}" from: CAS attributeName: phone # 可选的验证与发送限流 bucket4j: enabled: true blocking: false

八、故障排查建议

  1. 收不到任何令牌:优先检查communicationsManager.isMailSenderDefined()、isSmsSenderDefined()对应的底层发送器是否配置成功,以及 Principal 是否真的携带了attributeName中指定的属性(可用 CAS 的属性审计/调试日志确认);
  2. 提示"未找到收件人":检查认证源属性映射(Attribute Repository)是否把mail/phone等属性解析到 Principal;多属性名场景下确认每个属性都能解析;
  3. 令牌校验失败:检查timeToKillInSeconds是否过短,以及令牌长度是否过短导致生成碰撞;
  4. 频繁被限流:检查cas.authn.mfa.simple.bucket4j.*是否误开启,并参照 BaseBucket4jProperties.java 调整bandwidth与blocking(默认阻塞等待,blocking=false时请求会被立即拒绝)。

结语

Simple MFA 的通信策略是"属性驱动、多路可选、策略可编排"的:收件人地址来自 Principal 属性,投递渠道由TokenSharingStrategyOptions枚举组合决定,邮件/短信/语音/通知四类发送器均可独立启用与配置,再配合令牌时效、长度、字符类型与 bucket4j 限流,构成了一套完整且可实战的轻量 MFA 令牌分发体系。理解 CasSimpleMultifactorSendTokenAction 的编排逻辑与各属性模型的默认值,是正确落地这套机制的关键。

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

【免费下载链接】cas

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

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

相关推荐

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

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

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

立即咨询