Herald(鸦使):把验证码送到该送的人手里

¥0.00 库存: 999
立即购买

商品详情



  Herald(鸦使)是一个轻量的 OTP 和验证码服务。

  它负责创建一次“验证挑战”,生成验证码,通过邮件、短信、钉钉这类通道把验证码送出去,再在用户输入验证码之后完成校验。它可以接在 Stargate 和 Warden 后面,作为认证链路里的“送信人”;也可以独立运行,给内部后台、脚本、平台或其他服务提供统一的验证码能力。

  前两篇里,我们先聊了 Stargate,又聊了 Warden。

  Stargate 解决的是第一件事:有没有登录?

  它站在反向代理后面、业务服务前面,把认证逻辑前移到入口侧。后端服务不需要自己写登录页,不需要自己处理会话,也不需要在每个接口里重复判断“这个请求能不能进来”。

  Warden 解决的是第二件事:谁可以继续往下走?

  它不做登录页,也不发验证码。它维护一份准入名册,让 Stargate 或其他调用方可以用邮箱、手机号或 查询一个人是否存在,状态是不是 ,后续验证应该联系到哪里。

  这两步跑通之后,登录链路里还会剩下一个很现实的问题:怎么确认这次登录,确实是这个人在操作?

  如果用户输入的是邮箱,要不要发一封验证码邮件?如果输入的是手机号,要不要发一条短信?如果是企业内部协作场景,要不要走钉钉?验证码多久过期?输错几次以后要不要锁住?同一个用户反复点“重新发送”,要不要限流?发送失败了,是继续创建 challenge,还是直接让这次流程失败?

  这些问题也和登录有关,但不太适合继续塞进 Stargate,也不应该塞进 Warden。

  Stargate 应该继续守入口,管会话和登录流程。Warden 应该继续维护准入名单,回答“这个人现在还能不能继续往下走”。

  验证码的创建、发送、校验、过期、限流和审计,应该交给另一个更专门的服务。

  Herald 要补的,就是这一段。

  它不是完整 IAM,也不是营销短信平台,更不是要替代邮件网关、短信服务商或企业通知系统。它只先做好一件小事:把认证流程里的验证码和一次性口令,送到该送的人手里,并在用户提交之后给出清楚的验证结果。soulteary/herald

  项目地址:https://github.com/soulteary/herald

  如果 Stargate 是先把门装上,Warden 是让门开始认人,那么 Herald 做的事情,就是在门开之前,把这次临时通行码送出去。

  门开之前,鸦使先送信。

  很多内部服务一开始并不会做验证码。

  一个共享口令,一个管理员密码,一个只有几个人知道的内部地址,往往就能让最初的服务跑起来。HomeLab、测试环境、小团队内部工具、临时后台,第一天就接邮件、短信、限流、审计和多因素认证,确实有点重。

  这没什么问题。

  先把门装上,比继续裸奔要好得多。

  但只要使用时间拉长,服务变多,使用的人变多,共享口令就会慢慢不够用。于是你开始引入 Warden,用一份名单判断“这个人有没有资格继续登录”。

  名单解决的是资格问题。

  它能告诉你:这个邮箱在不在名单里,这个用户现在是不是 ,后续可以联系哪个邮箱或手机号。

  但它还没有解决另一个问题:这次操作,是不是这个人真的在操作?

  比如用户输入了 。Warden 查到了这个邮箱,状态也是 。接下来,登录流程通常还需要往这个邮箱、手机号或企业 IM 账号里发一个验证码。只有能收到验证码,并且能正确输入的人,才继续创建会话。

  这一步看起来很简单。第一反应往往是:在 Stargate 里写几行发邮件的代码不就行了?或者 Warden 既然已经查到手机号了,顺手调短信服务商不就好了?

  一开始当然可以这么做。

  但验证码链路很快会比想象中麻烦。

  验证码需要生命周期。它不能一直有效,通常几分钟后就应该过期。用户输错几次以后,也不应该继续无限尝试。

  验证码需要限流。一个用户、一个 IP、一个邮箱或手机号,都不能无限制地请求验证码。否则轻则刷爆短信费用,重则变成撞库、骚扰和探测入口。

  验证码需要发送通道。邮件、短信、钉钉、企业微信、飞书、Webhook,每一种通道的配置方式、失败方式和排查方式都不一样。

  验证码还需要审计。什么时候创建了 challenge,什么时候发送失败,什么时候验证失败,什么时候命中了限流,什么时候被锁住,这些事情只靠几行散落在日志里的文本,很难长期排查。

  还有一个容易被忽略的问题:验证码服务本身也需要服务间认证。能调用它的人,可能触发验证码发送,也可能参与登录流程。它不是一个应该公开给所有人随便调的接口。

  如果把这些都塞进 Stargate,入口服务会越来越重。它不再只是 ForwardAuth 和会话网关,还要维护验证码状态、接短信网关、处理邮件模板、做限流、写审计、管理 HMAC、处理 mTLS。

  如果把这些都塞进 Warden,名单服务也会开始跑偏。Warden 原本只应该回答“这个人是谁、状态是什么、后续验证应该联系哪里”。一旦它开始发送验证码,它就会关心模板、通道、过期时间、重试、锁定和审计。

  这不是我想要的方向。

  所以验证码这件事,也应该被拆出来。

  Herald 负责送信。

  它不判断这个人有没有资格登录,也不负责最终创建会话。它只负责在调用方已经决定“可以进入验证步骤”之后,创建一个 challenge,把验证码送出去,并在用户提交验证码时给出明确结果。

  拆开之后,整个链路会清楚很多:

  这样分开以后,排查问题时也更容易知道应该看哪里。

  遇到验证码和一次性口令的问题,常见做法大概有几种。

  第一种,是直接写在业务服务里。

  如果只有一个应用,这样当然可以。业务服务自己知道用户是谁,手机号是什么,什么时候应该发验证码,也能自己校验。

  但内部工具常常不是这样。

  很多服务只是 Grafana、文档站、测试面板、临时后台、脚本服务、第三方应用。你不一定能改它们的代码,也不一定值得为每一个服务都写一套验证码逻辑。

  最后很容易变成:这个服务用邮件验证码,那个服务用短信验证码,另一个服务还在用固定口令。过期时间不一样,失败次数不一样,日志格式不一样,排查方式也不一样。

  服务数量多起来之后,真正麻烦的不是“能不能发出去”,而是“每个地方到底按什么规则在发”。

  第二种,是把验证码写进认证入口里。

  这比每个服务都写一套要好一些。至少验证码逻辑集中到了 Stargate 这样的入口服务里,下游服务不用重复实现。

  但入口服务会变重。

  一开始只是发一封邮件。后面要支持短信,再后面要支持钉钉,再后来要加限流、锁定、审计、模板、多语言、HMAC、mTLS、Redis、多实例。到这一步,入口服务就不再只是入口了。

  Stargate 的核心问题应该是:这个请求是否已经登录?未登录时应该跳转到哪里?登录完成后如何创建会话并回到原服务?

  验证码可以出现在这个流程里,但它不应该长成 Stargate 的主要身体。

  第三种,是直接依赖某个短信或邮件服务商。

  比如业务代码直接调用某个短信 API,或者直接配置 SMTP 发邮件。

  这能跑起来,但会把服务商细节暴露给调用方。每个调用方都要知道短信 API 怎么签名、错误码怎么处理、邮件模板怎么写、失败要不要重试、调用频率怎么控制。

  一旦要换服务商,或者一个环境走邮件、另一个环境走短信,调用方就会被迫知道更多不该知道的东西。

  更理想的方式,是调用方只说:

  给这个 ,通过这个通道,向这个 destination 发送一次 验证码。

  至于背后是 SMTP、短信网关、钉钉机器人,还是某个内部通知系统,应该由验证码服务自己处理。

  第四种,是直接上完整身份系统。

  如果你已经有公司 SSO、IAM、OIDC、MFA 或完整身份中台,那么很多验证码和多因素认证问题确实应该交给它们。

  完整身份系统能处理的事情更多:账号生命周期、绑定关系、MFA 策略、登录风险、审计、协议、组织架构和权限治理。

  但很多内部服务并没有一开始就到这个规模。

  你可能只是想给几个内部服务加一道门,先从共享口令升级到名单,再让登录时多一步验证码。为了这个目标,第一天就搭完整 IAM、接回调、建用户库、配置策略和多因素认证,对很多 HomeLab、小团队和测试环境来说会显得太重。

  所以我想要的是一个中间方案。

  它不接管完整身份治理,不要求业务服务改代码,不把发送逻辑塞进 Stargate,也不让 Warden 变成验证码系统。

  它只先做好一件事:给内部认证链路提供一个稳定的 OTP 和验证码服务。

  Herald 站在这里,专门处理这一步。

  把 Herald 单独拆出来,最重要的原因不是“微服务化”,而是边界清楚。

  验证码这件事本身有自己的状态。

  一个 challenge 至少会经历这些阶段:

  这里每一步都和登录有关,但又不完全等于登录。

  Herald 不需要知道登录页长什么样,也不需要知道用户最终会被带到哪个服务。它只需要知道几个比较具体的东西:

  同样,Warden 不需要知道验证码是怎么发的。它只需要返回用户状态和联系方式。比如:

  Stargate 拿到这个结果以后,如果确认用户存在,并且状态是 ,再调用 Herald:

  Herald 创建 challenge,发送验证码,然后返回一个 。

  用户输入验证码后,Stargate 再把 和 交给 Herald 校验。校验通过,Stargate 创建会话;校验失败,Stargate 继续停留在验证流程。

  这样拆开之后,每个服务都不用知道太多。Stargate 不需要知道短信服务商怎么签名。Warden 不需要知道验证码多久过期。Herald 不需要判断这个人有没有资格登录。

  后面替换起来也会轻很多。验证码通道从邮件换成短信,主要改 Herald。用户名单从本地 JSON 换成远程 API,主要改 Warden。入口从单域名登录变成多域名会话,主要改 Stargate。

  不要让一个工具因为“顺手”就开始管太多。顺手写进去的时候很省事,后面拆出来的时候会很疼。

  Herald 的边界应该保持简单:

  除此之外,它不应该变成用户目录,不应该变成登录网关,也不应该替代完整身份系统。

  它只是认证链路里的鸦使,把该送的信送到该送的人手里。

  Herald 的工作方式可以先用一句话理解:

  调用方创建一个 challenge,Herald 生成验证码并发送;用户提交验证码后,调用方再让 Herald 校验这个 challenge。

  最常见的流程是:

  这里最关键的是 。

  调用方不应该自己保存明文验证码,也不应该把验证码塞进 Cookie 或前端状态里。它只需要保存或传递 。后续验证时,把用户输入的 一起交给 Herald 就够了。

  创建 challenge 的请求大概是这样:

  是调用方已经确认过的用户标识。它通常来自 Warden,也可以来自你自己的系统。 表示发送通道,可以是 、 或 。 是实际送达地址。邮件通道里它是邮箱,短信通道里它是手机号,钉钉通道里可以是钉钉用户 ID,或者在对应适配器支持的情况下使用手机号。 表示这次验证码的用途,比如 、、、。

  我建议从第一天就认真填写 。登录、重置、绑定、敏感操作二次确认,不应该全部混成一个默认用途。现在看只是多传一个字段,后面做审计、排查和策略调整时,会省很多事。

  返回结果大概是这样:

  告诉调用方验证码还有多久过期。 告诉调用方下一次可以重新发送前,需要等待多久。

  如果是本地测试,并且启用了 ,创建 challenge 的响应里还可能带上 。这对本地联调很方便,但生产环境一定不要打开。

  验证时,请求会更简单:

  验证成功时,Herald 会返回类似:

  对调用方来说,重点是 和 。

  确认返回成功,并且 和当前登录流程里期待的用户一致,再继续创建会话。不要只看 就往下走,尤其是一个页面里可能重新发起过多次 challenge 的时候。

  验证失败时,Herald 会返回:

  在验证阶段常见的是 、、、 等。创建 challenge 或重发验证码时,还可能遇到 、、、 这类错误。调用方可以根据这些原因做不同提示,也可以统一展示更模糊的错误信息,避免给攻击者太多细节。

  Herald 还支持撤销 challenge:

  用户取消登录、重新开始流程、绑定操作中途终止,或者你希望主动让某次验证码失效时,这个接口会很有用。

  整个过程中,Redis 很重要。Herald 会用 Redis 保存 challenge 状态、限流计数、幂等记录和锁定信息。生产部署时,建议给 Herald 使用独立 Redis 实例,或者至少使用独立数据库索引。它的数据和认证流程有关,不适合和一堆普通业务缓存混在一起。

  理解了工作方式之后,先把 Herald 跑起来。

  这一节先不接 Stargate,也不接 Warden,不接真实短信服务商。只做一件事:让 Herald 能启动,能创建 challenge,能在本地测试模式下拿到验证码,并完成一次校验。

  先把项目拉下来:

  创建一个用于本地测试的 :

  这份配置是本地体验用的,不是生产模板。

  里面有两处要特别注意。

  这只适合本地和测试环境。测试模式可能返回明文验证码,也可能开放测试取码接口。生产环境必须关掉。

  本地调试为了方便,可以把端口映射出来。真实部署时,Herald 更适合放在内部网络里,只允许 Stargate 或其他可信调用方访问。

  启动服务:

  看一下容器状态:

  再看健康检查:

  正常情况下,会看到类似:

  接着设置 API Key:

  创建一个测试 challenge:

  如果启用了测试模式,返回里可能会包含 :

  如果返回里没有 ,也可以在测试模式下用 challenge ID 查询:

  然后验证验证码:

  成功时,会看到类似:

  到这里,Herald 的最小链路就跑通了。

  我们已经确认了几件事:服务能启动,Redis 能连上,API Key 能保护接口,challenge 能创建,验证码能校验。

  这一步完成后,先不要急着接真实短信网关,也不要急着接 Stargate。

  先把最短链路跑顺。

  验证码链路一旦接入真实发送通道,排查范围会变大:可能是 Herald 配置问题,可能是 Redis 问题,可能是服务间鉴权问题,也可能是邮件、短信、钉钉服务本身的问题。

  先用测试模式确认 challenge 和验证流程,再接通道,排查会轻很多。

  Herald 的配置项不少,但第一次使用时,不需要一口气看完。

  我会先把它拆成三类。

  第一类,是最小链路一定会碰到的:

  第二类,是接真实通道时才需要认真看的:

  第三类,是生产化和长期运行时再看的:

  这样拆开以后,会好理解很多。第一天先别急着把所有能力都打开。

  Herald 默认监听 。

  本地测试时保持默认即可。如果你的习惯是不带冒号,也可以写成:

  Redis 是 Herald 的核心依赖,用来保存 challenge、限流计数、幂等记录和锁定状态。

  最小配置可以是:

  本地测试时,同一个 Compose 里的 Redis 就够了。

  生产环境里,建议给 Herald 使用专用 Redis,或者至少使用独立数据库索引。验证码数据虽然不是完整用户资料,但它直接参与认证流程,不应该和普通缓存混在一起。

  Herald 的接口不应该裸奔。

  创建 challenge 意味着可能触发验证码发送;验证 challenge 意味着参与登录流程。这类接口从第一天开始就应该有服务间认证。

  最简单的方式是 API Key:

  如果没有配置 API Key、HMAC 或 mTLS,Herald 可能会记录警告并允许未认证请求。这个行为只适合开发和测试环境,真实部署时不要依赖它。

  调用 Herald 的业务接口时,把 API Key 放在 请求头里即可。比如前面创建 challenge 的请求里,我们已经这样带上了它:

  本地测试可以用占位值。真实环境不要继续使用 ,也不要把真实 API Key 写进仓库。

  如果 Herald 已经成为认证链路里的关键服务,可以考虑 HMAC。

  单密钥配置:

  多密钥配置:

  多密钥的好处是可以做轮换。调用方通过 指定使用哪个 key,服务端可以在一段时间里同时接受新旧密钥。等迁移完成后,再移除旧密钥。

  HMAC 请求一般会带上:

  HMAC 签名不是只检查几个请求头,而是用 和密钥计算签名。也就是说,调用方和 Herald 两边要对时间、服务名和请求体保持一致。

  这里的 很重要。它可以降低请求被重放的风险。

  更高要求的环境,可以继续上 mTLS。它的部署成本更高,不一定是第一天必须做的事,但如果 Herald 位于更复杂的网络环境里,或者被多个关键服务调用,就值得考虑。

  我的习惯是这样推进:

  不要第一天就把链路做得太复杂,但也不要长期停留在“只要能访问到服务就能调用”的状态。

  几个最常用的配置可以先这样:

  决定验证码多久过期。内部工具登录场景里,5 分钟通常比较容易接受。更敏感的操作,比如绑定、重置、二次确认,可以根据实际场景缩短。

  和 用来控制输错次数。验证码不能无限尝试。同一个 challenge 失败太多次以后,就应该锁住。

  这里要注意,锁住的是这次 challenge,不是完整账号体系。Herald 不知道用户完整状态,也不应该替代 Warden。它只负责这次验证挑战本身。

  用来控制重发冷却。用户可能没收到邮件,短信可能延迟,页面也可能被误关。重发是必要的,但不能无限制。前端可以根据 Herald 返回的 展示倒计时,而不是让用户反复点击。

  默认可以从 6 位开始。验证码位数不应该孤立看,过期时间、失败次数、限流和服务间认证都要一起看。

  建议尽早配置。不要把登录、重置、绑定、二次确认都混成一个用途。

  比如:

  现在分清楚,后面审计和排查会清楚很多。

  验证码服务不应该没有限流。

  哪怕只是内部环境,也应该先配一个基础值:

  大致含义是:

  这三类限流都很重要。

  只按用户限流,可能挡不住同一个 IP 扫很多目标。

  只按 IP 限流,可能误伤 NAT 后面的一组用户。

  只按 destination 限流,可能挡不住同一个攻击者换很多邮箱或手机号。

  多维限流不是为了把系统弄复杂,而是验证码接口天然容易被滥用。不要等短信费用被刷了,或者邮箱被打爆了,再想起来补这一层。

  本地联调时,可以启用:

  这样创建 challenge 时,响应里可能带 ,也可以通过测试接口查询验证码。

  这对没有真实短信或邮件服务的环境很方便。你可以先验证 Stargate、Warden、Herald 之间的流程,不必真的发短信。

  但生产环境必须关闭:

  测试模式会暴露明文验证码。不要因为“反正只有内网访问”就把它留在生产环境。

  发送验证码可能失败。

  SMTP 配置不对,短信网关超时,钉钉服务不可达,都可能导致发送失败。

  Herald 支持发送失败策略:

  更适合本地测试或非关键场景。即使发送失败,也可以继续创建 challenge,方便你先调通链路。

  真实登录场景里,我更倾向于:

  如果验证码没发出去,就不要创建一个用户永远收不到的 challenge。

  这里没有绝对答案,要看场景。但至少要有意识地选择,不要把本地调试用的 无意间带到生产登录链路里。

  Herald 本地跑通以后,下一步通常是接真实通道。

  这里不要贪多。

  先选一个通道,把它跑顺,再加第二个。

  否则验证码没收到时,你很难判断到底是 Herald 的问题、Redis 的问题、服务间鉴权的问题,还是邮件、短信、钉钉服务本身的问题。

  邮件通常是最容易起步的通道。

  你可以先用内置 SMTP:

  然后创建 email challenge:

  如果邮件收不到,先不要急着看 Stargate。

  先看 Herald 日志,再单独测试 SMTP,再确认 、账号、密码、端口和网络。

  如果你希望把 SMTP 凭证和发送逻辑单独拆出去,可以使用 :

  这样 Herald 不直接保存 SMTP 凭证,而是通过 HTTP 把邮件发送请求转给 。

  这个方式更符合“每个服务只管一件事”的思路。Herald 管 challenge 和验证,herald-smtp 管 SMTP 凭证和邮件发送。

  短信适合需要更及时、更贴近手机验证的场景。

  但短信也更容易带来成本和滥用风险。

  在接短信之前,先确认限流、重发冷却和失败次数已经配置好:

  短信通道通常建议通过外部 HTTP API 网关接入:

  我不建议让 Herald 直接理解每一个短信厂商的所有细节。

  不同服务商的签名方式、模板参数、地域、错误码、频率限制都不一样。如果把这些全部写进 Herald,Herald 很快会变重。

  更好的方式,是把短信服务商差异放在一个短信网关里。阿里云、腾讯云或其他服务商的密钥也放在那里。Herald 只知道自己要发一条短信,不直接保存具体厂商密钥。

  这样后面换服务商、做灰度、做多通道兜底,都会轻很多。

  如果团队已经在钉钉里工作,钉钉验证码会很自然。

  配置大概是:

  然后创建 challenge 时使用:

  这时钉钉凭证和发送逻辑放在 里。Herald 不直接保存钉钉凭证。

  这类通道很适合内部系统,尤其是手机号不一定适合保存或发送、邮件又不够及时的环境。

  不过也要注意:不要把一个人的企业 IM 账号当成唯一身份来源。

  身份和准入仍然应该由 Warden 或你的用户系统判断。

  Herald 只负责送达和校验。

  Herald 也可以作为 TOTP 代理,把 Authenticator 相关请求转给 :

  启用后,调用方可以通过同一个 Herald 地址处理 OTP 和 TOTP 流程。

  比如:

  不过第一次使用 Herald 时,我不建议一上来就接 TOTP。

  先把邮件或短信验证码跑通,再考虑 Authenticator 这类更强验证方式。

  Herald 不一定要接在 Stargate 后面。

  它本身就是一个独立的 OTP 和验证码服务。任何内部系统,只要有服务间认证,都可以调用它。

  比如,你有一个内部后台,需要在用户执行敏感操作前做二次确认。

  后台可以先创建一个 challenge:

  用户收到验证码后,后台再校验:

  如果返回:

  后台再继续执行敏感操作。

  如果返回:

  就让用户重新发起验证。

  如果返回:

  就不要继续让用户尝试当前 challenge。

  独立使用 Herald 时,我会注意三件事。

  第一,调用方要自己确认用户是谁。

  Herald 不负责判断 是不是一个存在的用户,也不判断他是否应该继续操作。这应该由你的业务系统、Warden 或其他用户系统先完成。

  第二,调用方要保存好 和上下文。

  比如这次 challenge 对应的是登录、重置密码,还是敏感操作二次确认。验证成功后,也要确认返回的 和当前流程里期待的用户一致。

  第三,调用方不要自己记录明文验证码。

  验证码应该由 Herald 生成和校验。除了本地测试模式,不要把验证码明文返回给调用方,也不要把验证码打进日志。

  如果把三者组合起来,登录流程会更完整。

  最初只有 Stargate 时,流程大概是:

  接入 Warden 后,流程变成:

  再接入 Herald 后,流程会变成:

  这里最重要的还是边界。

  Warden 查到用户,不代表一定要发验证码。Stargate 需要先判断 是不是 。

  Herald 校验成功,也不代表它自己创建会话。创建会话仍然是 Stargate 的事情。

  Warden 返回手机号或邮箱,也不代表它要知道验证码有没有发出去。发送、过期、失败次数和限流都是 Herald 的事情。

  如果用环境变量表达,组合关系大概可以是这样:

  具体变量名要以调用方当前实现为准,但关系是清楚的:

  在 Compose 里,Herald 也应该和 Warden 一样放在内部网络里:

  这只是示意配置,重点是:

  用户能看到登录页,但看不到 Warden,也看不到 Herald。

  这条链路里,真正对外的是入口。

  送信人不应该站在门外给所有人随便喊。

  如果你准备把 Herald 放进自己的认证链路里,我建议按小步来。

  先用测试模式跑通 challenge。

  这一步只验证 Herald 自己的核心能力:创建 challenge,拿到测试验证码,提交 code,验证成功。

  如果这一步都没跑通,就不要急着接 Stargate、Warden 或通道服务。

  接着打开服务间认证。

  本地测试也建议带 API Key:

  等链路跑通后,再考虑 HMAC 或 mTLS。

  然后只接一个真实通道。

  先邮件,或者先短信。不要同时接三个通道。验证码没收到时,排查链路要尽量短。

  单通道跑通之后,再加第二个通道。

  再把 用起来。

  不要所有场景都用 。登录、重置、绑定、敏感操作二次确认,最好从一开始就分开。

  接着确认限流。

  这些值后面可以根据实际情况调整,但不要没有。

  最后再接 Stargate 和 Warden。

  排查时按顺序拆开:

  不要从“浏览器登录失败”开始猜。

  链路越长,越要一段一段确认。

  Herald 在认证链路里的位置很敏感。

  它不保存完整用户资料,也不创建登录会话,但它能触发验证码发送,能验证一次登录或敏感操作的关键步骤。

  所以它不应该裸奔。

  本地测试时使用:

  很方便。

  但真实部署时,Herald 更适合只跑在内部网络里。

  访问链路应该是:

  用户不应该直接访问 Herald。

  如果确实需要跨机器访问,也应该通过 VPN、内网、服务网格、受控反向代理或安全组限制访问范围。

  上线前确认:

  不要让 出现在生产响应里。

  不要让 在生产环境可用。

  不要把验证码打进日志。

  测试模式是为了本地联调,不是为了省事。

  至少设置:

  更稳的方式是 HMAC:

  更高要求的环境,可以考虑 mTLS。

  这里的原则很简单:• 能创建验证码的人,必须是可信调用方。• 能验证 challenge 的人,也必须是可信调用方。

  Herald 会在 Redis 里放 challenge、限流、幂等和锁定信息。

  生产环境建议:

  不要把 Herald 的 Redis 直接暴露到公网。

  也不要把它和一堆不受控的业务缓存混在一起。

  不要把这些写进 Git:

  本地可以用 ,但 不应该提交。

  示例文件可以写:

  真实值应该来自 Secret 管理、部署平台环境变量、Docker Secret、Kubernetes Secret 或专门的密钥系统。

  Herald 的日志和审计很有用。

  但它们也可能成为敏感信息泄露点。

  不要完整记录:

  可以考虑启用 destination 脱敏:

  生产环境里,日志是为了排查问题,不是为了复制一份验证码和联系方式。

  Herald 可以暴露 Prometheus 指标。

  这对长期运行很有用,比如观察 challenge 创建数量、发送成功率、验证成功率、失败原因、限流命中和 Redis 延迟。

  但 也不应该随便暴露给外部用户。

  更合适的方式,是只允许 Prometheus、内网监控或受控网段访问。

  本地测试时:

  可以帮助你先跑通流程。

  但真实登录环境里,如果验证码没发出去,通常不应该继续创建 challenge。

  可以考虑:

  这样发送失败时,调用方能更明确地知道这次验证流程没有开始成功。

  如果只是内部使用,我觉得 Herald 的起步部署可以是这样:

  对应到 Compose,大概像这样:

  这里没有 。

  这意味着 Herald 不会直接暴露到宿主机端口。它只给同一个内部网络里的 Stargate 或可信调用方访问。

  本地调试可以临时加端口映射,调试完成后再去掉。以及,注意不要把调试配置直接带进生产环境。

  到这里,Stargate、Warden、Herald 三件事就串起来了。

  Stargate 解决入口问题,先把门装上。

  Warden 解决名单问题,让门开始认人。

  Herald 解决验证问题,把验证码和消息送到该送的人手里。

  这三个工具放在一起,并不是想重新做一套完整 IAM。完整 IAM 要处理的是账号生命周期、组织架构、协议、权限、策略、审计、多因素认证和治理体系。

  Stargate、Warden、Herald 更像是几个可以独立工作的基础设施小工具。

  每个工具只先解决一个具体问题:

  你可以先只用 Stargate,用共享口令把几个内部服务保护起来。

  等共享口令不够用了,再接 Warden,用邮箱、手机号或 判断谁还能继续登录。

  等登录流程需要更明确的验证,再接 Herald,把验证码发出去,并让它负责过期、失败次数、限流、撤销和审计。

  再往后,如果服务更多、要求更高,再考虑 HMAC、mTLS、专用 Redis、持久化审计、Prometheus 指标、OpenTelemetry、TOTP,或者直接接入更完整的身份体系。

  这条路径不追求第一天就把功能堆满,而是每加一步,都知道它解决了什么问题。

  第一天不要太重。

  但也不要一直停在共享口令里。

  Herald 想补的,就是“已经知道这个人可以继续往下走”之后的那一步:

  把这次验证送到他手里。它不决定谁能进。它不创建最终会话。它做的事情不大,但边界很清楚:

  创建验证码,送出验证码,校验验证码。

  先装门,再认人,最后送信。

  如果这个项目对你有帮助,欢迎到 GitHub 顺手点个 Star:https://github.com/soulteary/herald星门启闭,守望验身,鸦使送信。

  下一篇系列文章,让我们聊聊如何简单的使用这三个软件。

  --EOF

  我们有一个小小的折腾群,里面聚集了一些喜欢折腾、彼此坦诚相待的小伙伴。

  我们在里面会一起聊聊软硬件、HomeLab、编程上、生活里以及职场中的一些问题,偶尔也在群里不定期的分享一些技术资料。

  关于交友的标准,请参考下面的文章:致新朋友:为生活投票,不断寻找更好的朋友

  当然,通过下面这篇文章添加好友时,请备注实名和公司或学校、注明来源和目的,珍惜彼此的时间 :D关于折腾群入群的那些事

  如果你觉得内容还算实用,欢迎点赞分享给你的朋友,在此谢过。

  如果你想更快的看到后续内容的更新,请戳 “点赞”、“分享”、“收藏” ,这些免费的鼓励将会影响后续有关内容的更新速度。

  本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 署名 4.0 国际 (CC BY 4.0)

  本文作者: 苏洋

  创建时间: 2026年05月30日 统计字数: 22312字 阅读时间: 45分钟阅读 本文链接: https://soulteary.com/2026/05/30/herald-lightweight-otp-and-verification-code-delivery-service.html