Logto 对接阿里云短信认证服务(MAS):aliyun-sms-mas 连接器从配置到源码的完整实战
【免费下载链接】logto🧑🚀 Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto
本文以 Logto 仓库中 阿里云短信认证服务连接器 的官方文档为核心,完整讲解该连接器的产品定位、与标准短信服务的区别、使用限制、阿里云控制台准备步骤与 JSON 配置写法,并结合连接器源码深入剖析号码校验、模板匹配、HMAC-SHA1 签名与错误映射等底层实现。读完后,你可以独立完成该连接器的接入配置,并理解其“仅作为发送通道”的架构设计与边界。
产品定位:短信认证服务(MAS)与短信服务(SMS)有何不同
阿里云短信认证服务(Message Authentication Service,下称 MAS)是专为验证码场景设计的服务,隶属于号码认证服务(Phone Number Verification Service)产品家族,与标准短信服务(SMS)是两条不同的产品线。两者关键区别如下:
| 维度 | 短信服务(SMS) | 短信认证服务(MAS) |
|---|---|---|
| 签名与模板 | 需自行申请签名和模板 | 使用系统赠送的签名和模板(无需申请) |
| 支持的消息类型 | 验证码、通知、营销等多种类型 | 仅验证码场景 |
| 号码范围 | 支持国际号码 | 仅中国大陆手机号 |
| 接入复杂度 | 需管理签名/模板审核与生命周期 | 简化接入,内置频控机制 |
MAS 的典型优势是免签名/模板申请流程、接入简单,但代价是功能收窄(仅验证码、仅限国内号码)。选择哪个产品,取决于你的业务是否需要向港澳台及海外用户发短信。
注意:本连接器仅作为短信发送通道使用
该连接器仅将阿里云短信认证服务用作短信发送通道:Logto 自行生成的验证码通过 API 参数(
TemplateParam)传入,不集成阿里云的验证码生成、校验及生命周期管理功能。这是为保持 Logto 现有“生成并校验”架构而做出的设计选择——使用本连接器时,不应期望验证码由阿里云侧管理。
使用限制:接入前必须确认的三条红线
MAS 有以下硬性限制,如果你的目标用户群不满足,请在接入前切换到 阿里云短信服务连接器(同目录下,支持国际号码):
- 仅限中国大陆手机号:目前仅支持中国移动、中国联通和中国电信的手机号码(中国大陆)。
- 不支持国际及港澳台:不支持中国台湾、中国香港、中国澳门及海外地区。
- 不支持自定义签名:自 2025 年 11 月 12 日起,阿里云发布公告,因运营商签名实名制政策管控要求,号码认证产品下所有使用短信验证码触达的认证方式均不支持使用自定义签名下发短信(具体恢复时间另行通知)。此外,阿里云可能会不定期轮换赠送签名,请留意签名更新的邮件和短信提醒,及时在 Logto 连接器配置中更新
signName。
在阿里云控制台准备资源
接入前需在阿里云侧完成以下准备(服务默认开通,流程很短):
- 登录阿里云账号。
- 进入短信认证服务(号码认证服务)控制台。
- 服务开通后,可直接使用系统赠送的签名和模板,无需申请。
- 从头像菜单进入 “AccessKey 管理”,创建一对 AccessKey(后续用于连接器的 API 签名鉴权)。
编写连接器 JSON 配置
在 Logto 控制台的连接器配置中填写以下四部分:
- 填写
accessKeyId与accessKeySecret,即上一步创建的 AccessKey 密钥对; - 填入
signName—— 请从阿里云控制台(号码认证 → 短信认证参数配置 → 赠送签名配置)复制一个当前有效的赠送签名; - 使用系统赠送的模板 CODE 配置
templates数组,将每个usageType映射到对应的templateCode:
| UsageType | 模板 CODE | 说明 |
|---|---|---|
| SignIn | 100001 | 登录验证 |
| Register | 100001 | 注册验证 |
| ForgotPassword | 100003 | 重置密码 |
| Generic | 100001 | 通用验证 |
| UserPermissionValidation | 100005 | 用户权限验证 |
| BindNewIdentifier | 100002 | 绑定新标识符 |
| OrganizationInvitation | 100001 | 组织邀请 |
| MfaVerification | 100001 | MFA 验证 |
| BindMfa | 100001 | 绑定 MFA |
完整的示例配置如下(可直接作为配置基线,替换三处占位符即可使用):
{ "accessKeyId": "your-access-key-id", "accessKeySecret": "your-access-key-secret", "signName": "<your-gift-signature>", "templates": [ { "usageType": "SignIn", "templateCode": "100001" }, { "usageType": "Register", "templateCode": "100001" }, { "usageType": "ForgotPassword", "templateCode": "100003" }, { "usageType": "Generic", "templateCode": "100001" }, { "usageType": "UserPermissionValidation", "templateCode": "100005" }, { "usageType": "BindNewIdentifier", "templateCode": "100002" }, { "usageType": "OrganizationInvitation", "templateCode": "100001" }, { "usageType": "MfaVerification", "templateCode": "100001" }, { "usageType": "BindMfa", "templateCode": "100001" } ] }配置项类型说明
| 名称 | 类型 | 描述 |
|---|---|---|
| accessKeyId | string | 阿里云 Access Key ID |
| accessKeySecret | string | 阿里云 Access Key Secret |
| signName | string | 签名名称(系统赠送签名) |
| templates | Template[] | 模板配置数组,元素为{ usageType, templateCode } |
源码解析(一):元数据、表单与配置校验
连接器元数据与控制台表单
在 constant.ts 中,defaultMetadata定义了连接器在 Logto 控制台中的呈现方式:
- 连接器
id为aliyun-short-message-auth-service,target为aliyun-sms-mas; formItems声明了四个表单字段:accessKeyId、accessKeySecret、signName(均为必填文本框)和templates(必填 JSON 编辑器,默认值即上表所示的 9 条模板映射);signName字段的表单描述中内置了控制台路径提示(号码认证 → 短信认证参数配置 → 赠送签名配置),降低了配置出错概率。
配置校验:为什么模板必须覆盖 9 种 UsageType
配置并非“能解析就行”。types.ts 中的aliyunSmsMasConfigGuard(zod schema)在z.array(templateGuard)基础上额外做了一个refine校验:9 种必填的usageType(Register、SignIn、ForgotPassword、Generic、UserPermissionValidation、BindNewIdentifier、OrganizationInvitation、MfaVerification、BindMfa)必须全部出现在 templates 数组中,缺失时会报错并列出具体缺少的类型名。
这解释了上文示例 JSON 为什么包含 9 条映射——缺任何一条都会被配置校验拒绝。每次实际发送前,validateConfig还会再次用该 schema 校验运行期配置(见 index.ts 第 82 行)。
源码解析(二):发送主流程——号码校验、模板匹配与固定有效期
发送入口是 index.ts 中工厂函数返回的sendMessage,其调用链为:配置校验 → 模板匹配 → 号码解析 → 参数组装 → 调用sendSmsVerifyCode→ 解析响应。几个关键设计点:
1. 手机号解析:严格限定 +86,自动剥离区号
parseMainlandChinaPhoneNumber 基于@logto/shared提供的PhoneNumberParser(底层为 libphonenumber-js)完成解析:
- 先处理
0086国际拨号前缀(归一化为+86开头); - 校验号码合法且国家码必须为
86,否则抛出invalid_request_parameters错误并附带原始号码; - 返回不带区号的国内段号码(如
13012345678),因为 MAS 的PhoneNumber参数要求如此,国家编码通过独立的CountryCode: '86'参数传递。
2. 模板匹配与缺失兜底
getConfigTemplateByType(type, config)按当前消息类型(如SignIn)从配置的 templates 中取模板,取不到时抛出TemplateNotFound错误。
3. 固定 10 分钟有效期与 payload 过滤
// Logto uses fixed 10-minute expiration time // min parameter is used in template: "您的验证码是${code},有效期${min}分钟,请勿告诉他人。" const masPayload = { ...filteredPayload, min: '10' };参见 index.ts 第 100-L102 行:系统赠送模板的文案包含${code}与${min}两个占位符,连接器会把 payload 中的locale字段剥离(MAS API 不需要),并固定注入min: '10',即 Logto 验证码的 10 分钟有效期会直接写入短信文案。
源码解析(三):API 请求与 HMAC-SHA1 签名机制
端点与静态参数
与标准短信服务(dysmsapi.aliyuncs.com)不同,MAS 使用号码认证服务专属端点:
- messageAuthEndpoint:
https://dypnsapi.aliyuncs.com/; - staticConfigs:
Format=json、RegionId=cn-hangzhou、SignatureMethod=HMAC-SHA1、SignatureVersion=1.0、Version=2017-05-25。
sendSmsVerifyCode 将Action: 'SendSmsVerifyCode'、静态参数与业务参数(AccessKeyId、CountryCode、PhoneNumber、SignName、TemplateCode、TemplateParam)合并后发起请求。业务参数的类型定义见 types.ts 的 SendSmsVerifyCode,其中TemplateParam为 JSON 字符串(如{"code":"123456","min":"10"})。
阿里云 POP 网关签名实现
utils.ts 完整实现了阿里云 POP API 的 V1 签名规范,这是该连接器最“硬核”的部分:
- 阿里云专用 URL 转义(escaper):在标准
encodeURIComponent基础上,额外将!"'()*+编码为%21%22%27%28%29%2A%2B——这是阿里云网关与 RFC 3986 的已知差异,+不转义会导致签名校验失败; - 规范化查询串(getSignature):参数按键名字母序排序并逐一对键值转义后拼接,再构造
METHOD & encode("/") & encode(CanonicalizedQueryString)的待签名串,用AccessKeySecret + "&"作为密钥做 HMAC-SHA1,输出 Base64; - 请求封装(request):自动注入
SignatureNonce(randomUUID 防重放)与秒级精度的Timestamp(YYYY-MM-DDThh:mm:ssZ),所有参数值统一转为字符串,最终以application/x-www-form-urlencoded的 POST 表单提交,签名结果放入Signature字段。
对应的单元测试(utils.test.ts)覆盖了特殊字符转义、签名的确定性/差异性、Base64 输出格式与参数排序规则。
源码解析(四):错误码映射与测试佐证
MAS API 的响应经 sendSmsVerifyCodeResponseGuard 用 zod 校验后,连接器按 错误映射逻辑 将阿里云错误码转换为 Logto 标准连接器错误:
| 阿里云 Code | Logto 错误码 | 含义 |
|---|---|---|
OK | (成功,返回{ Code, Message, ...rest }) | 发送成功 |
BUSINESS_LIMIT_CONTROL | rate_limit_exceeded | 触发频控(如单号码每日上限) |
FREQUENCY_FAIL | rate_limit_exceeded | 发送频率超限 |
MOBILE_NUMBER_ILLEGAL | invalid_request_parameters | 号码不合法 |
其他(如FUNCTION_NOT_OPENED) | general | 通用错误,携带阿里云Message |
HTTP 层错误(HTTPError)同样会被解析响应体并抛出general错误。这套映射确保控制台与终端用户侧拿到的是语义清晰的 Logto 错误,而不是裸的厂商错误码。
单元测试 index.test.ts 对上述行为做了逐项验证,可作为实现事实的依据:
- 发送请求参数精确匹配:
AccessKeyId、CountryCode: '86'、剥离区号后的PhoneNumber、SignName、TemplateCode: '100001'以及TemplateParam: '{"code":"...","min":"10"}'; - 9 种
TemplateType到模板 CODE 的映射逐条断言; - 号码解析:
86...、+86...、0086...三种写法均能正确剥离为国内段号码;美国号码(如16502530000)与非法输入(如abc)被拒绝且不会发起任何 API 调用; - 错误映射:
BUSINESS_LIMIT_CONTROL/FREQUENCY_FAIL→rate_limit_exceeded,MOBILE_NUMBER_ILLEGAL→invalid_request_parameters,其余 →general。
实现事实与运行环境小结
- 包名与版本:
@logto/connector-aliyun-sms-masv1.1.3(见 package.json),运行环境要求 Node.js^22.14.0; - 依赖仅
@logto/connector-kit、@logto/shared(workspace 依赖)、got(HTTP 客户端)与zod(配置/响应校验),无重量级 SDK——签名算法为纯手写实现; - 源码结构:index.ts(主流程)、send-verify-code.ts(API 封装)、utils.ts(签名与请求)、types.ts(配置与响应 schema)、constant.ts(元数据与端点);
- 更新
signName的方式:当阿里云轮换赠送签名后,在 Logto 控制台编辑该连接器配置、替换signName即可,无需改动其他配置。
参考
- 连接器文档:packages/connectors/connector-aliyun-sms-mas/README.md
- 国际号码替代方案:packages/connectors/connector-aliyun-sms/README.md
- 连接器单元测试:packages/connectors/connector-aliyun-sms-mas/src/index.test.ts、packages/connectors/connector-aliyun-sms-mas/src/utils.test.ts
- 阿里云侧文档:短信认证服务产品文档与
SendSmsVerifyCode(dypnsapi 2017-05-25 版本)API 参考,可在阿里云官方帮助中心检索“短信认证服务”或“SendSmsVerifyCode”查阅。
【免费下载链接】logto🧑🚀 Authentication and authorization infrastructure for SaaS and AI apps, built on OIDC and OAuth 2.1 with multi-tenancy, SSO, and RBAC.项目地址: https://gitcode.com/GitHub_Trending/lo/logto
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考