InfoQ Geekathon 大模型技术应用创新大赛 了解详情
写点什么

不就是个短信验证嘛,还真挺复杂的

  • 2019-06-05
  • 本文字数:3548 字

    阅读完需:约 12 分钟

不就是个短信验证嘛,还真挺复杂的

支撑子域是为了项目成功必须要处理的问题,但由于没有现成、成熟的解决方案,必须要定制,费时费力。

如果能恰当地识别支撑子域的边界,形成”可复用”的”解决方案”,就可以将其从支撑子域简化为通用子域,降低成本和风险。

不就是个短信验证嘛,有这么复杂吗?

前几天安全专家马伟发布了《不就是个短信登录API嘛,有这么复杂吗?》,文章从“新增手机号和短信验证码登录”简单的一句话需求最终演变为


故事卡-274

作为用户,我可以通过手机号和短信验证码登录,以便于更方便的登录。

安全验收标准:

  • 短信验证码有效期 2 分钟

  • 验证码为 6 位纯数字

  • 每个手机号 60 秒内只能发送一次短信验证码,且这一规则的校验必须在服务器端执行

  • 同一个手机号在同一时间内可以有多个有效的短信验证码

  • 保存于服务器端的验证码,至多可被使用 3 次(无论和请求中的验证码是否匹配),随后立即作废,以防止暴力攻击

  • 短信验证码不可直接记录到日志文件

  • (可选)发送短信验证码之前,先验证图形验证码是否正确

  • (可选)集成第三方 API 做登录保护


实际上,根据我的经验,还可以再加一些验收条件


  • 应该可以通过配置白名单的方式,只向特定手机号码发送验证码,以免在非生产环境测试时发生打扰真实用户的事故

  • 应该可以通过配置 By Pass 的方式,在特定环境禁用短信验证码发送,并总是验证通过,以便在非生产环境节约短信配额


一个小小的需求可以衍生出如此之多的验收条件,而且其中不少是非功能性的(不容易识别的、不容易实现的),以至于有同学感叹:


厉害,短信验证这个事,如果有人做成整套解决方案直接调用就好了,就像 keycloak 一样。

运用子域进行战略设计

那么短信验证是否能成为”整套解决方案”呢,我们可以使用领域驱动设计中子域分类的框架来分析:


  • 核心子域:它是一个唯一的、定义明确的领域模型,你要在这里进行战略投资,并在一个明确的限界上下文中投入大量资源去精心打磨通用语言。它是组织中最重要的项目,因为这将是你与其他竞争者的区别所在。正是因为你的组织无法在所有领域都出类拔萃,所以你必须把核心域打造成组织的核心竞争力。做出这样的决定需要对核心域进行深入地学习与理解,而这需要承诺、协作与试验。这是组织最需要在软件中倾斜其投资的方向。

  • 支撑子域:这类建模方式提倡的是“定制开发”,因为找不到现成的解决方案。你对它的投入无论如何也达不到与核心域相同的程度。你也许会考虑使用外包的方式实现此类限界上下文,以避免因错误的认为其具有战略意义而进行巨额的投资。这类软件模型仍旧非常重要,核心域的成功离不开它。

  • 通用子域:通用子域的解决方案可以采购现成的,也可以采用外包的方式,亦或是由内部团队实现,但我们不用为其分配与核心域同样优质的研发资源,甚至都不如支撑子域。请注意不要把通用子域误认为是核心域。你并不希望对其投资过甚。当讨论一个正在实施 DDD 的项目时,我们最有可能讨论的是核心域。

  • ——《DDD 精粹:运用子域进行战略设计》Vaughn Vernon


可以发现:


  1. 核心子域是没有必要或者说不应该尝试开发“可复用”的”整套解决方案”的,因为”可复用“、”整套解决方案“意味着高度的标准化,也就意味着可以以较低成本复制,就不可能成为核心竞争力。

  2. 通用子域应该采用,而且往往也能找到“可复用”的”整套解决方案”,以便降低成本和风险。

  3. 支撑子域则显得很”鸡肋”,由于没有现成、成熟的解决方案,必须定制,但它又不是项目的核心价值。因此,如果能恰当地识别支撑子域的边界,形成”可复用”的”解决方案”,就可以将其从支撑子域简化为通用子域,进一步降低成本和风险。


我认为短信验证就是一个好例子,短信验证自身没有独立的价值,但没有它,某些重要的功能会缺乏保护。但目前只能找到发送短信的 SDK,而缺乏对于”发送-验证”这个相对标准化的问题域的支持。

解决方案的形态是什么样的

在微服务的大潮下,如果想要复用短信验证的能力,最先想到的是开发一个短信验证服务,开放 API 给 Consumer 验证手机号码或是短信登录,名字我都想好了,叫sms-otp(OPT 为 one time password 缩写)。



(sms-otp 服务)


如果我是甲方 IT 部门,可能就这么做了,找到一个软件集成商实现sms-otp就行了。


作为数字化转型服务厂商,ThoughtWorks 的想法会再进一步,是否还有更通用的方法?


ThoughtWorks 可能需要为很多客户交付短信验证服务,并且出于专业要求,我们并不会把为 A 客户定制的代码复制到 B 家使用,这时候一个开箱即用的微服务是最佳选择吗?


如果还有其他的“通用”需求呢?例如支付宝支付、微信登录呢,微服务的数量就开始膨胀了。在一些项目中,部分客户的 IT 基础设施比较滞后,这类项目未必适合以微服务启动。那有没有更灵活的方案,既可以在单体应用中开箱即用,又可以按需扩展为独立服务呢?

如果存在不确定性,不妨做个 MVP

提到开箱即用,近几年在 Java 业界最火的就是Spring Boot了,Auto Configuration大大提高了新应用搭建的速度,在需要定制时又不失灵活性。我觉得这是把好锤子,来敲两下看看是不是找对了钉子?



我们针对短信验证推出了自定义的 Spring Boot Starter,大名


通过 starter,既可以将解决方案”嵌入”单体应用,也可以快速启动新的微服务。


以下是一个简单的接入示例,为项目添加 Starter:


compile group: "com.github.hippoom:sms-verification-starter:${latestVersion}"
# 如果需要使用开箱即用的Redis验证码存储compile "org.springframework.data:spring-data-redis:2.1.2.RELEASE"# 如果需要使用开箱即用的Aliyun短信服务compile("com.aliyun:aliyun-java-sdk-core:4.0.6")compile("com.aliyun:aliyun-java-sdk-dysmsapi:1.1.0")
复制代码


为应用注入配置项:


# application-{profile}.properties
# 如果使用开箱即用的Aliyun短信服务daming.sms.provider=aliyundaming.aliyun.accessKeyId={your key id}daming.aliyun.accessKeySecret={your key secret}daming.aliyun.sms.signature={your text} # 阿里云短信服务的签名,可以在控制台找到,如是中文,请转为Unicodedaming.aliyun.sms.templateCode={your code} #阿里云短信服务的模板Code,可以在控制台找到
# 设置私钥地址,此私钥会用来签名被验证过的手机号码daming.jwt.privateKeyFileLocation=/home/your-app/sms-verification-private.der
复制代码


启动应用,并请求验证码:


>curl -H 'Content-Type: application/json' -XPOST ${host}:${port}/api/sms/verification/code -d '{"mobile": "${your mobile}"}'
复制代码


在收到验证短信后,尝试验证:


>curl -H 'Content-Type: application/json' -XDELETE ${host}:${port}/api/sms/verification/code -d '{"mobile": "${your mobile}","code":"${the code}"}'{"token":"{a very long string}"}%
复制代码


在 Response 中可以得到一个 JWT,前端应用将该 JWT 提交给 Consumer 应用,Consumer 应用使用私钥对应的公钥即可验证该手机号码实现业务目标(如登录或保存验证过的手机号码)。

一些自问自答

如果是 Starter 的话,如何灵活定制呢?


既然这些 Starter 都是解决支撑/通用子域的问题,那么其领域规则、业务流程是比较固定、不易变化的。需要灵活定制的部分其实是技术实现,使用端口和适配器架构可以将这两部分隔离开,使用适配器扩展/变更技术解决方案。举个例子:



大名的端口和适配器架构


各个出口端口(右侧橙色板块的 Port)作为扩展点,定制的 Driven Adapter 通过 Spring 注入。


为什么要绑定技术栈?非 Java 技术栈怎么办?


可以使用该 Starter 快速搭建一个微服务。。。


有没有前端的开箱即用方案 ?


还没有,我不是前端专家,但我猜测前端的开箱即用方案可以做成类似于Ant DesignElement UI但更专用的组件?

总结

  1. 支撑子域是为了项目成功必须要处理的问题,但由于没有现成、成熟的解决方案,它必须定制,费时费力

  2. 如果能恰当地识别支撑子域的边界,形成”可复用”的”解决方案”,就可以将其从支撑子域简化为通用子域,降低成本和风险

  3. 短信验证是从支撑子域简化为通用子域的好例子,Project Daming(中文为大名),是我们推出的短信验证的解决方案,它的目标是将短信验证从支撑域简化为通用域,它以自定义的 spring boot starter 出现,可以帮助团队在单体应用中快速嵌入短信验证功能,也可以快速启动一个短信验证的微服务

花絮

计算机科学中最难的两个问题,一个是分布式系统中的缓存系统,另一个则是起名字,为了避免第二个问题,我们决定以各个城市的著名景点周边道路作为项目代号。


  • 大名,是上海市大名路的简称,位于外滩附近


本文转载自 ThoughtWorks 洞见


原文链接


https://insights.thoughtworks.cn/sms-verification/

作者简介

周宇刚,拥有 10 年的 JAVA EE 开发经验,在 ThoughtWorks 担任高级咨询师。在加入 ThoughtWorks 之前,在一家国内领先的航旅企业担任架构师,专注于持续交付实践和大型企业应用架构治理。


活动推荐:

2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。

2019-06-05 08:0014280
用户头像

发布了 85 篇内容, 共 21.6 次阅读, 收获喜欢 232 次。

关注

评论

发布
暂无评论
发现更多内容

GaussDB NoSQL架构设计分享

华为云开发者联盟

nosql 云原生 GaussDB 超融合 多模数据库

在线极限词,敏感词,违禁词查询工具

入门小站

工具

架构训练营 - 模块四

Geek_9de3de

架构实战营

Apollo选型及优势介绍

小鲍侃java

11月日更

dart系列之:在dart中使用packages

程序那些事

Java flutter dart 程序那些事 11月日更

containerd镜像层过大问题

ilinux

TypeScript 是弱类型

HoneyMoose

【Flutter 专题】06 图解基础【登录】页面并学习相关 Widget

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 11月日更

到底什么样的ABAP系统能运行Fiori应用

Jerry Wang

SAP abap Fiori 11月日更

纪念林徽因

linux实战清理挖矿病毒kthreaddi

入门小站

Linux

版本化ASP.NET Core WebApi

喵叔

11月日更

告诉你!操作系统是个大骗子!

博文视点Broadview

CSS奇技淫巧之滤镜

Augus

CSS 11月日更

KubeMeet 深圳站完整议题出炉!快来 get 云原生边缘计算硬核技术干货

阿里巴巴云原生

阿里云 云原生 边缘计算 KubeMeet 线下活动

Vue进阶(幺玖幺):ECharts 实现地图功能

No Silver Bullet

Vue eCharts 11月日更

博文推荐|Pulsar 的消息存储机制和 Bookie 的 GC 机制原理

Apache Pulsar

存储 Apache Pulsar 消息中间件 Apache BookKeeper bookie

企业数字化转型与SAP云平台

Jerry Wang

SAP 11月日更 SAP数字化转型 SAP云平台

面对DNS劫持,只能坐以待毙吗?

阿里巴巴云原生

阿里云 云原生 云拨测 DNS劫持 产品活动

Golang Gin 框架参数解析介绍(三)

liuzhen007

11月日更

TypeScript 类型系统

HoneyMoose

大数据训练营一期0908作业

朱磊

大数据训练营一期0919作业

朱磊

Podman是什么

耳东@Erdong

容器 11月日更 Podman

ThreadPoolExecutor 任务提交原码分析

new life

ThreadPoolExecutor

flutter开发中vscode插件推荐1

坚果

flutter vscode 11月日更

大数据训练营一期0829作业

朱磊

时序序列分类算法概述

云智慧AIOps社区

大数据 算法 时间序列 智能运维

【Promise 源码学习】第九篇 - Promise.resolve 和 Promise.reject 的实现

Brave

源码 Promise 11月日更

详解低延时高音质:丢包、抖动与 last mile 优化那些事儿

声网

音频体验 RTE技术详解 last mile 优化

火出边际的Serverless,你居然还不了解?

华为云开发者联盟

云计算 Serverless 运维 云原生 敏捷

  • 扫码添加小助手
    领取最新资料包
不就是个短信验证嘛,还真挺复杂的_语言 & 开发_Thoughtworks洞见_InfoQ精选文章