news 2026/9/14 1:57:39

NocoBase 短信验证码(SMS OTP)实战指南:从添加验证器、服务商配置到自定义扩展

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NocoBase 短信验证码(SMS OTP)实战指南:从添加验证器、服务商配置到自定义扩展

NocoBase 短信验证码(SMS OTP)实战指南:从添加验证器、服务商配置到自定义扩展

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

短信验证码(SMS OTP)是 NocoBase 验证管理(Verification)插件内置的验证类型,用于生成一次性动态密码(OTP)并通过短信下发给用户,支撑短信验证码登录、双因素身份认证(2FA)等场景。本文基于 NocoBase 官方文档 验证:短信 与@nocobase/plugin-verification插件源码,完整覆盖「添加验证器 → 管理员配置服务商 → 用户绑定/解绑 → 验证与安全机制 → 扩展自定义短信服务商」的全流程,并给出源码级的实现依据与错误处理细节。

功能背景:验证管理中心中的 SMS OTP

1.6.0-alpha.30开始,NocoBase 原来的「验证码」功能升级为「验证管理」:管理员可以在验证管理中心接入不同的用户身份验证方式,用户在个人验证管理中绑定对应的验证方式后,即可在绑定了该验证器的验证场景(如短信登录、2FA)中进行身份验证。

在验证管理中心中,sms-otp是与totp(TOTP 认证器)并列的默认验证类型。用户认证模块(如短信登录)依赖验证模块提供的验证器;验证模块则负责登录之外的各种风险操作场景下的身份验证。整体架构可参考 验证管理。

从源码结构看,服务端插件入口位于 Plugin.ts,其中smsOTPProviderManager(短信服务商注册器)与verificationManager(验证场景注册器)是 SMS OTP 功能的两个核心注册点。

添加短信验证器

  1. 进入「验证管理」页面(系统管理 → 验证管理)。
  2. 点击「添加」,在验证类型列表中选择SMS OTP
  3. 按提示选择短信服务商并填写服务商配置(见下一节),保存后即可启用。

对应地,客户端为 sms-otp 验证类型注册了三类界面组件:验证表单(VerificationForm)、管理员配置表单(AdminSettingsForm)和绑定表单(BindForm),见 sms/index.ts:

export const smsOTPVerificationOptions = { components: { VerificationForm, AdminSettingsForm, BindForm, }, };

管理员配置:服务商、密钥与短信模板

在验证器的管理员配置中,目前内置支持两家短信服务商(见 sms.md):

  • 阿里云短信
  • 腾讯云短信

短信模板参数要求

在服务商管理后台配置短信模板时,必须为验证码预留参数位:

  • 阿里云配置示例:您的验证码为:${code}
  • 腾讯云配置示例:您的验证码为:{1}

这一点与源码实现严格对应:发送验证码时data只包含code字段。阿里云实现将参数以 JSON 形式传给templateParam,腾讯云实现将code作为第一个模板参数(TemplateParamSet: [data.code]),见 sms-aliyun.ts 与 sms-tencent.ts。

各服务商的配置字段

从两个内置服务商的构造函数读取的配置项来看,管理员配置表单需要填写的字段如下:

阿里云(sms-aliyun

字段说明
accessKeyId阿里云 AccessKey ID
accessKeySecret阿里云 AccessKey Secret
endpoint短信服务 API 域名
sign短信签名名称(映射到signName
template短信模板 Code(映射到templateCode

腾讯云(sms-tencent

字段说明
secretId/secretKey腾讯云 API 凭证
region地域
endpointAPI 接入域名
SignName短信签名
TemplateId短信模板 ID
SmsSdkAppId应用 SDK AppID

服务端在实例化服务商前,会先对settings做一次app.environment.renderJsonTemplate(settings)渲染(见 sms/index.ts 中的getProvider()),因此配置项支持全局变量(Global Variable)写法——这也是客户端配置表单使用TextAreaWithGlobalScope组件的原因。

用户绑定与解绑

添加验证器后,用户可以在「个人 → 验证管理」中绑定验证手机号:填写手机号 → 获取短信验证码 → 输入验证码完成绑定。绑定成功后,即可在绑定了该验证器的验证场景中进行身份验证。

解绑手机号时,需要通过已绑定的验证方式先完成一次身份验证,防止手机号被恶意解绑。

源码层面:

  • 绑定动作由OTPVerification.bind()完成,它会先以verifiers:bind动作走一遍完整的验证码校验流程再落库,见 otp-verification/index.ts:
async bind(userId: number, resource?: string, action?: string): Promise<{ uuid: string; meta?: any }> { const { uuid, code } = this.ctx.action.params.values || {}; await this.verify({ resource: resource || 'verifiers', action: action || 'bind', boundInfo: { uuid }, verifyParams: { code }, }); return { uuid }; }
  • 绑定信息中的uuid字段即手机号本身;服务端对外展示时会做脱敏处理——只保留末 4 位,其余以*填充(getPublicBoundInfo())。
  • 绑定手机号时会执行validateBoundInfo(),手机号为空时抛出「Not a valid cellphone number, please re-enter」错误。

验证流程与安全机制(源码解析)

验证码的生成与下发

smsOTP资源的create/publicCreate动作负责下发验证码(见 sms-otp.ts),关键逻辑:

  1. 参数校验:请求需携带verifier(验证器名称)和action(验证场景动作名,格式为resource:action),服务端校验两者在验证器表与验证场景注册表中均存在,否则返回 400。
  2. 防重发限流:若同一接收人(receiver)在当前场景下已存在未使用且未过期的验证码记录,则直接返回 429(RateLimit),并提示剩余冷却秒数。
  3. 生成 6 位数字验证码randomInt(999999)转字符串并padStart(6, '0')补齐 6 位。
  4. 调用服务商发送await provider.send(receiver, { code }),成功后写入otpRecords表(randomUUID主键、actionreceivercodeexpiresAtstatusverifierName),返回{ id, expiresAt }

验证码校验与防暴力破解

OTPVerification.verify()的校验逻辑(见 otp-verification/index.ts):

  • 有效期:默认expiresIn = 120秒,查找记录时要求expiresAt晚于当前时间;
  • 单次有效:记录status必须为CODE_STATUS_UNUSED,校验成功后由onActionComplete()将其更新为CODE_STATUS_USED,不可重复使用;
  • 失败次数限制:以${resource}:${action}:${receiver}为键在缓存中计数,maxVerifyAttempts = 5,连续失败超过 5 次后返回 429(「Too many failed attempts. Please request a new verification code」),并提示用户重新获取;校验成功后立即counter.reset(key)清零;
  • 缓存 TTL 会取该验证码剩余有效期,保证计数随验证码一起过期。

错误映射

发送阶段(provider.send()抛错时)按错误名映射为不同的 HTTP 响应:

服务商抛出的错误含义接口响应
InvalidReceiver手机号不合法(如阿里云isv.MOBILE_NUMBER_ILLEGAL、腾讯云InvalidParameterValue.IncorrectPhoneNumber400InvalidReceiver
RateLimit服务商侧频控(如阿里云isv.BUSINESS_LIMIT_CONTROL、腾讯云各类LimitExceeded.*429
其他未知发送失败(详情仅记录到日志,不暴露给用户)500

验证 API 与数据表

对扩展插件或前端调用方而言,SMS OTP 相关接口为:

  • POST /api/smsOTP:create(及匿名可用的smsOTP:publicCreate):values支持verifier(验证器名)、action(场景动作)、uuid(接收手机号,登录等匿名场景使用)。
  • verifiers表:保存管理员创建的验证器(名称、标题、验证类型sms-otpoptions中的providersettings);
  • otpRecords表:保存每一次下发的验证码记录(receivercodeexpiresAtstatusverifierName),见 otp-records.ts。

扩展自定义短信服务商

除阿里云与腾讯云外,开发者可以插件形式扩展其他短信服务商,官方开发文档见 扩展短信服务商。核心分两步:

客户端:注册服务商配置表单

用户选择该服务商类型后展示的配置表单需要开发者自行注册,通过plugin.smsOTPProviderManager.registerProvider(name, { components: { AdminSettingsForm } })完成,表单字段可参考内置的AliyunSettings/TencentSettings(client/otp-verification/sms)。

服务端:实现 SMSProvider 并注册

验证插件已封装创建 OTP 的完整流程,开发者只需继承SMSProvider基类(定义见 providers/index.ts)实现与服务商交互的发送逻辑:

class CustomSMSProvider extends SMSProvider { constructor(options) { super(options); // options 为客户端表单提交的配置对象 const { accessKeyId, accessKeySecret, endpoint } = this.options; // ... } async send(phoneNumber: string, data: { code: string }) { // 调用第三方短信 API 发送验证码 // 建议抛错时设置 error.name: // 'InvalidReceiver' → 映射为 400 手机号不合法 // 'RateLimit' → 映射为 429 频控 } }

然后通过registerProvider注册,注意客户端与服务端的 name 必须一致:

import { Plugin } from '@nocobase/server'; import PluginVerificationServer from '@nocobase/plugin-verification'; import { tval } from '@nocobase/utils'; class PluginCustomSMSProviderServer extends Plugin { async load() { const plugin = this.app.pm.get('verification') as PluginVerificationServer; plugin.smsOTPProviderManager.registerProvider('custom-sms-provider-name', { title: tval('Custom SMS provider', { ns: namespace }), provider: CustomSMSProvider, }); } }

服务端SMSOTPVerification.getProvider()会按验证器options.providersmsOTPProviderManager.providers中取出对应类,并用渲染后的settings实例化,因此只要注册名与管理员在验证器中选择的 provider 一致即可被调用。

测试用例验证

官方测试 verify.test.ts 使用一个MockSMSProvider注册为mock服务商,覆盖了上述全部安全机制:

  • 调用smsOTP:createotpRecords中出现对应记录(status: 0未使用);
  • 同一手机号在未使用验证码有效期内重复请求,返回 429;
  • 错误验证码返回 400(「Verification code is invalid」);
  • 正确验证码校验通过,记录状态更新为 1(已使用);
  • 将记录expiresAt置为过期后校验同样返回 400;
  • 连续 6 次错误校验中,前 5 次 400、第 6 次起 429,且此时正确验证码也无法通过(需重新获取)。

总结

NocoBase 的短信验证能力围绕「验证器 + 场景动作」两个抽象组织:管理员在验证管理中创建 SMS OTP 验证器并配置阿里云/腾讯云密钥与模板;用户绑定手机号后即可在短信登录、2FA 等场景使用;底层则通过 6 位数字验证码、120 秒有效期、单次有效、5 次失败锁定与服务商频控映射构成了完整的防暴力破解链路。扩展新的短信服务商只需实现SMSProvider.send()并在两端registerProvider注册,即可无缝接入现有验证体系。

【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 1:56:42

Android Fragment生命周期详解与最佳实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 1:54:41

鲸鱼迁徙算法求解大规模物流路径规划问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 1:51:18

Ollama本地模型前端接入指南:从API调用到流式对话实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 1:49:39

PP-MattingV2 ONNX Runtime WinForms部署:实现高性能抠图

简介&#xff1a;面向 C# 桌面应用开发者&#xff0c;提供在 WinForm 中部署 PP-MattingV2 人像抠图 ONNX 模型的完整源码工程。项目基于 VS2019、.NET Framework 4.7.2&#xff0c;结合 OpenCvSharp4.8.0 与 ONNX Runtime 1.16.3 完成模型推理与图像处理&#xff0c;适合需要快…

作者头像 李华