- 后端
- 认证鉴权
- 单点登录
【免费下载链接】cas
Apereo CAS - Identity & Single Sign On for all earthlings and beyond.
导读
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方法,其执行逻辑大致如下:
- 解析已认证的
Principal,并getOrCreateToken获取(或重新生成)令牌; - 通过
tokenCommunicationStrategy.determineStrategy(token)确定本次采用的通信策略集合; - 依次尝试
tryToSendSms、tryToSendEmail,再根据策略集合判断是否发送平台通知(isNotificationSent)与拨打语音电话(CasSimpleMultifactorMakePhoneCall); - 只要任一渠道投递成功,就把令牌写入票据注册表(
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.html | false | 正文是否按 HTML 渲染(依赖底层邮件实现,对默认 JavaMailSender 有效) |
cas.authn.mfa.simple.mail.validateAddresses | false | 是否校验收件地址格式(依赖底层实现) |
cas.authn.mfa.simple.mail.priority | 1 | 邮件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.attributeName | phone | 从 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."所指的能力。通知策略的触发条件有两个:
- 通信策略集合包含
NOTIFICATION选项(默认全渠道策略即包含); - 通知发送器已定义(
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 |
| Slack | Notifications-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.timeToKillInSeconds | 30 | 令牌在 CAS 服务器中的存活时间(秒)。这是令牌有效期,过期后用户输入的验证码将无法通过校验 |
cas.authn.mfa.simple.token.core.tokenLength | 6 | 生成令牌的长度。注意:长度过短(小于 6 位字符或数字)在高并发下容易产生碰撞;CAS 会尝试重试生成唯一令牌,但若始终无法生成唯一值,最终会放弃 |
cas.authn.mfa.simple.token.core.tokenType | NUMERIC | 令牌字符类型,可选NUMERIC(纯数字)、ALPHANUMERIC(字母数字混合)、ALPHABETIC(纯字母) |
示例配置:
cas: authn: mfa: simple: token: core: timeToKillInSeconds: 60 tokenLength: 8 tokenType: NUMERIC5.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八、故障排查建议
- 收不到任何令牌:优先检查
communicationsManager.isMailSenderDefined()、isSmsSenderDefined()对应的底层发送器是否配置成功,以及 Principal 是否真的携带了attributeName中指定的属性(可用 CAS 的属性审计/调试日志确认); - 提示"未找到收件人":检查认证源属性映射(Attribute Repository)是否把
mail/phone等属性解析到 Principal;多属性名场景下确认每个属性都能解析; - 令牌校验失败:检查
timeToKillInSeconds是否过短,以及令牌长度是否过短导致生成碰撞; - 频繁被限流:检查
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.
相关推荐
CAS Passwordless 无密码认证的通知机制:邮件与短信 Token 投递实战
CAS Passwordless 无密码认证的通知机制:邮件与短信 Token 投递实战 导读 Apereo CAS 的无密码(Passwordless)认证依
后端认证鉴权单点登录Quivr通知系统:邮件、短信、推送通知集成
Quivr通知系统:邮件、短信、推送通知集成 引言:智能助手的沟通桥梁 在当今信息爆炸的时代,一个优秀的AI助手不仅需要强大的知识处理能力,更需要及时有效的通知
人工智能AI 应用大模型RAG后端前端跨平台字体渲染不一致性问题的PingFangSC字体解决方案
跨平台字体渲染不一致性问题的PingFangSC字体解决方案 PingFangSC字体包是一个专为技术决策者和开发者设计的跨平台中文字体解决方案,提供六种字重的
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考