基于 Twilio Authy 为 SFTPGo 集成一次性密码(OTP)双因素认证:三种 Hook 实战方案
后端网络存储【免费下载链接】sftpgoFull-featured and highly configurable SFTP, HTTP/S, FTP/S and WebDAV server - S3, Google Cloud Storage, Azure Blob项目地址https://gitcode.com/gh_mirrors/sf/sftpgo点击查看免费下载SFTPGo 提供了丰富的认证扩展机制外部认证 Hook、Check Password Hook、键盘交互认证本文以仓库 examples/OTP/authy 目录下三个官方示例为主线完整讲解如何接入 Twilio Authy API 实现基于 TOTP 软令牌的双因素认证2FA。读完本文你将掌握 Authy 应用的创建、Authy 用户的注册与映射、令牌校验判据并能基于三个示例分别实现密码 动态令牌键盘交互认证、令牌即密码外部认证、固定密码 令牌Check Password 认证三种实战方案。方案概述与适用场景Twilio Authy 提供了一套 REST API用于管理 Authy 用户并在服务端校验用户手机上 Authy App 生成的离线 TOTP 验证码软令牌。在 SFTPGo 中引入 Authy可以让用户登录时除了常规凭据外再输入一个动态令牌从而显著提升账户安全性。仓库 examples/OTP/authy 给出了三种互补的集成思路Keyboard interactive authentication键盘交互认证面向 SFTP/SSH 协议先要求输入密码再要求输入 Authy 令牌两步校验完成 2FAExternal authentication外部认证把 Authy 令牌直接当作 SFTPGo 用户的密码提交由外部认证 Hook 调用 Authy API 校验Check password hook检查密码 Hook用户输入固定密码 7 位 Authy 令牌组合串Hook 负责校验令牌部分SFTPGo 内置机制校验固定密码部分。三种方式都遵循同一个 Authy API 调用模型区别仅在于 SFTPGo 认证链路上的挂载点不同。以下先介绍共用的前置步骤。第一步在 Twilio Console 创建 Authy 应用集成前需要先在 Twilio Console 中创建 Authy Application参见 Twilio Console Authy 应用管理页面。创建成功后你会获得一个 API KeyAUTHY_API_KEY后续所有 Authy API 调用都要通过 HTTP 头X-Authy-API-Key携带该 Key。创建完成后可用如下命令验证应用是否注册成功export AUTHY_API_KEYyour api key here curl https://api.authy.com/protected/json/app/details -H X-Authy-API-Key: $AUTHY_API_KEY若请求返回应用详情 JSON 且状态正常说明 API Key 与应用的绑定关系有效可以进入下一步。第二步注册 Authy 用户并建立与 SFTPGo 账号的映射每个最终用户都需要在 Authy 侧注册为独立用户一个 Authy 用户对应一部手机上安装的 Authy App并通过邮箱、手机号与国别代码进行绑定curl -XPOST https://api.authy.com/protected/json/users/new \ -H X-Authy-API-Key: $AUTHY_API_KEY \ --data-urlencode user[email]userdomain.com \ --data-urlencode user[cellphone]317-338-9302 \ --data-urlencode user[country_code]54成功响应大致如下其中user.id是后续令牌校验的关键标识{message:User created successfully.,user:{id:xxxxxxxx},success:true}拿到user.id后需要把它与对应的 SFTPGo 账号关联起来。README 给出的推荐做法之一是把 Authy 用户 ID 存进 SFTPGo 用户的additional_info字段。这样在 Hook 中读取用户名后即可通过additional_info反查对应的 Authy ID无需维护额外映射表。三个示例程序则采用更直接的静态映射结构SFTPGoUsername/AuthyID/AuthyAPIKey例如 keyint/main.gotype userMapping struct { SFTPGoUsername string AuthyID int64 AuthyAPIKey string }示例代码在init()中硬编码映射并明确注释仅供演示生产环境建议通过数据库查询等方式动态获取keyint/main.go。注册完成后用户手机上的 Authy App 即可离线生成 TOTP 动态令牌。第三步令牌校验的判定条件核心判据Authy 提供令牌校验端点用户输入 Authy App 上显示的 7 位令牌后通过 HTTP GET 请求校验export TOKENTOTP you read from Authy app export AUTHY_IDuser id curl -i https://api.authy.com/protected/json/verify/${TOKEN}/${AUTHY_ID} \ -H X-Authy-API-Key: $AUTHY_API_KEYREADME 明确指出校验成功必须同时满足两个条件这也是所有 Hook 程序判断令牌是否有效的唯一依据HTTP 响应码必须是200JSON 响应体中必须包含键success且其值为字符串true。{message:Token is valid.,token:valid,success:true}三个示例程序均用同一套 Go 代码实现该校验逻辑以 extauth/main.go 为例url : fmt.Sprintf(https://api.authy.com/protected/json/verify/%v/%v, password, m.AuthyID) req, err : http.NewRequest(http.MethodGet, url, nil) req.Header.Set(X-Authy-API-Key, m.AuthyAPIKey) httpClient : http.Client{Timeout: 10 * time.Second} resp, err : httpClient.Do(req) // ... if resp.StatusCode ! http.StatusOK { printResponse() // status code 200 is expected } // ... if authyResponse[success].(string) true { printResponse(username) }注意几点工程细节HTTP 客户端设置了 10 秒超时生产环境应设置合理的读超时令牌值被直接拼入 URL 路径段/verify/{token}/{user_id}若担心特殊字符问题可在生产实现中做 URL 转义响应解析使用map[string]interface{}并断言success为字符串类型。四种挂载点的选择从源码看 SFTPGo 认证 Hook 机制SFTPGo 的认证扩展机制可以从 internal/dataprovider/dataprovider.go 源码中印证。认证 Hook 的配置集中在数据提供者的ProviderConf中包括external_auth_hook、check_password_hook、keyboard_interactive_auth_hook、pre_login_hook等dataprovider.go 附近。每种 Hook 既可以是外部可执行程序也可以是HTTP 端点配置值以http开头时走 HTTP 路径见 getPasswordHookResponse 与 getExternalAuthResponse。Hook 程序通过环境变量接收凭据信息核心变量包括SFTPGO_AUTHD_USERNAME登录用户名SFTPGO_AUTHD_PASSWORD登录密码外部认证与 Check Password Hook 使用由 getPasswordHookResponse 注入SFTPGO_AUTHD_IP客户端 IPSFTPGO_AUTHD_PROTOCOL协议类型SSH/FTP/WebDAV/HTTP 等SFTPGO_AUTHD_PUBLIC_KEY、SFTPGO_AUTHD_TLS_CERT、SFTPGO_AUTHD_USER用户 JSON等供外部认证 Hook 在需要时使用。理解这个机制后Authy 三个示例分别挂载在三条不同链路上下面逐一剖析。方案一Keyboard Interactive 认证密码 Authy 令牌两步 2FA适用协议SFTP/SSH。该方案要求用户先输入 SFTPGo 密码认证程序通过后再要求输入 Authy 令牌形成两步双因素。在 SFTPGo 服务端需要开启键盘交互认证并配置 Hook对应 internal/sftpd/server.go 中的keyboard_interactive_authentication与keyboard_interactive_auth_hook配置项{ sftpd: { keyboard_interactive_authentication: true, keyboard_interactive_auth_hook: /path/to/keyint } }键盘交互认证的完整调用链在源码中清晰可见ssh.KeyboardInteractiveCallback被注册后server.go转入dataprovider.CheckKeyboardInteractiveAuth进而执行doKeyboardInteractiveAuthdataprovider.go对于非 HTTP 配置会启动外部程序executeKeyboardInteractiveProgram通过stdout 输出 JSON 提问、stdin 读取用户回答的方式完成多轮交互dataprovider.go 附近的循环处理。示例 keyint/main.go 完整展示了这种程序 - SFTPGo - 客户端三方对话协议。核心是keyboardAuthHookResponse结构体type keyboardAuthHookResponse struct { Instruction string json:instruction,omitempty Questions []string json:questions,omitempty Echos []bool json:echos,omitempty AuthResult int json:auth_result CheckPwd int json:check_password,omitempty }交互流程如下程序先从SFTPGO_AUTHD_USERNAME环境变量读取用户名并查找映射找不到映射直接以非零码退出拒绝登录。输出第一轮提问CheckPwd: 1表示这一轮答案由 SFTPGo 内置密码校验接管checkPwdQuestion : keyboardAuthHookResponse{ Instruction: This is a sample keyboard authentication program that ask for your password Authy token, Questions: []string{Your password: }, Echos: []bool{false}, CheckPwd: 1, AuthResult: 0, }示例为演示起见用bufio.Scanner从 stdin 读到回答OK才继续真实实现中可让 SFTPGo 直接校验密码随后输出第二轮提问CheckPwd: 0答案由程序自己消费checkTokenQuestion : keyboardAuthHookResponse{ Questions: []string{Authy token: }, Echos: []bool{false}, CheckPwd: 0, AuthResult: 0, }读取用户输入的令牌调用 Authy verify API校验成功后调用printAuthResponse(1)输出{auth_result:1}并退出码 0否则auth_result为 -1 并退出码 1。提示示例中的if response ! OK仅为演示 stdin 读取逻辑生产实现应让 SFTPGo 完成密码校验check_password: 1程序只负责令牌环节。方案二外部认证 HookAuthy 令牌即密码适用协议SSH、FTP、WebDAV、HTTP 等所有支持密码登录的协议。该方案把 Authy 动态令牌直接当作 SFTPGo 用户的密码由外部认证 Hook 在收到密码后调用 Authy API 校验校验通过则返回或创建一个SFTPGo 用户对象。配置示例sftpgo.json 中的数据提供者部分{ data_provider: { external_auth_hook: /path/to/extauth, external_auth_scope: 1 } }external_auth_scope控制 Hook 作用于哪些登录方式0 表示全部1 表示仅密码登录示例代码中也对此做了呼应——非密码登录方式直接返回空用户拒绝extauth/main.gopassword : os.Getenv(SFTPGO_AUTHD_PASSWORD) if len(password) 0 { // login method is not password printResponse() return }外部认证 Hook 的 stdout 输出必须是合法的 SFTPGo 用户 JSON见 executeExternalAuthProgram 之后的解析逻辑。示例定义了最小用户结构type minimalSFTPGoUser struct { Status int json:status,omitempty Username string json:username HomeDir string json:home_dir,omitempty Permissions map[string][]string json:permissions }其中Status: 1表示用户启用Permissions[/] []string{*}授予根目录全部权限HomeDir临时指向系统临时目录extauth/main.go。关键点示例假设 SFTPGo 用户已预先存在// we assume that the SFTPGo already exists, we only check the one time token因此只做令牌校验若需由 Hook 现场创建用户还需输出更多字段如公钥、虚拟目录等可参考 SFTPGo 完整的 User JSON 结构。校验流程与前面的一致读取SFTPGO_AUTHD_PASSWORD作为令牌 → 调用 Authy verify → HTTP 200 且success true时输出用户 JSON 并以退出码 0 结束否则输出空用户名 JSON 并以退出码 1 结束。源码 dataprovider.go 还显示该 Hook 能拿到SFTPGO_AUTHD_USERNAME、SFTPGO_AUTHD_PASSWORD、SFTPGO_AUTHD_IP、SFTPGO_AUTHD_PROTOCOL、SFTPGO_AUTHD_PUBLIC_KEY、SFTPGO_AUTHD_TLS_CERT等环境变量可用于更精细的映射决策。方案三Check Password Hook固定密码 Authy 令牌组合串适用协议所有支持密码认证的协议。该方案不改变用户输入习惯之外的体验用户输入的密码是一个固定字符串后紧跟 7 位 Authy 令牌的组合串。Hook 拆出令牌部分交给 Authy API 校验校验通过后通过部分成功状态把固定密码部分交还给 SFTPGo 内置机制继续校验两层校验全部通过才允许登录。配置示例{ data_provider: { check_password_hook: /path/to/checkpwd, check_password_scope: 0 } }check_password_scope决定 Hook 在哪些协议下生效0 表示全部协议1/2/4 分别表示仅 SSH、仅 FTP、仅 WebDAV见 isCheckPasswordHookDefined。Check Password Hook 的响应协议定义在源码 dataprovider.gotype checkPasswordResponse struct { // 0 KO, 1 OK, 2 partial success, -1 not executed Status int json:status // for status 2 this is the password to check against the one stored // inside the SFTPGo data provider ToVerify string json:to_verify }status 0直接拒绝status 1完全通过status 2部分成功SFTPGo 将把to_verify字段中的密码与数据提供者中存储的密码比对status -1未执行例如协议不在check_password_scope范围内。示例 checkpwd/main.go 的实现要点按Authy 令牌固定为 7 位的约定拆分输入pwd : password[:pwdLen-7]、authyToken : password[pwdLen-7:]长度不足 7 位直接返回status: 0main.go。用拆出的令牌调用 Authy verify API校验成功则返回status: 2并把固定密码放回to_verify让 SFTPGo 继续与存储的密码比对main.goif authyResponse[success].(string) true { printResponse(2, pwd) } printResponse(0, )程序在输出前会把to_verify替换为[redacted]以防日志泄露密码main.go这是一个值得在生产实现中保留的好习惯。在 SFTPGo 内部该 Hook 由executeCheckPasswordHook调度dataprovider.go程序方式通过环境变量SFTPGO_AUTHD_USERNAME/SFTPGO_AUTHD_PASSWORD/SFTPGO_AUTHD_IP/SFTPGO_AUTHD_PROTOCOL接收输入getPasswordHookResponse。方案对比与选型建议维度Keyboard InteractivekeyintExternal AuthextauthCheck Password Hookcheckpwd适用协议主要是 SFTP/SSH所有支持密码登录的协议所有支持密码登录的协议用户输入密码、令牌分两步输入仅令牌作为密码固定密码 令牌连写密码校验方SFTPGo 内置check_password: 1由 Hook 自行决定示例未校验密码SFTPGo 内置status: 2to_verify令牌校验方Authy APIAuthy APIAuthy API对 SFTPGo 用户要求用户必须已存在于数据提供者示例假定用户已存在也可由 Hook 创建用户必须已存在且存储的密码须与固定部分一致Hook 挂载点keyboard_interactive_auth_hookexternal_auth_hookcheck_password_hook选型建议需要真正意义上的两步交互体验且主要面向 SFTP 时选方案一需要统一覆盖 FTP/WebDAV/HTTP 等所有协议时方案二最简洁希望在保留 SFTPGo 原生密码校验的同时叠加动态令牌、且不改变协议兼容性时方案三最合适。注意事项与生产化建议示例程序的定位README 明确强调这三个示例是演示性质程序不适用于生产环境。生产实现应基于它们重写自己的 Hook并注意以下几点。性能考量如果性能是关注点优先使用 HTTP 形式的 Hook配置值写成http://...端点即可避免每次认证都 fork 一个外部进程SFTPGo 源码对两种形式的分流逻辑可见 dataprovider.go。映射数据落地示例的静态mapping仅用于演示生产环境应把SFTPGoUsername ↔ AuthyID映射存入数据库或直接复用 SFTPGo 用户对象的additional_info字段。超时与错误处理Authy API 调用应设置合理超时示例为 10 秒并对网络错误、非 200 响应、JSON 解析失败全部按认证失败处理。凭证保护AUTHY_API_KEY不应硬编码进程序应从环境变量或密钥管理系统注入输出to_verify前进行脱敏。SFTPGo 内置 2FA值得特别留意的是SFTPGo 自身也内置了 2FA 支持README 结尾的警告即提示这一点。内置方案支持 Web 界面的 TOTP 绑定与恢复码等能力若你希望少维护一套外部服务可优先评估内置 2FA 是否满足需求Authy 方案则适合已在使用 Twilio 生态、或需要把 SFTP 之外的认证体系统一到 Authy 的场景。进一步阅读三个示例的完整 Go 源码keyint/main.go、extauth/main.go、checkpwd/main.go认证 Hook 的配置项与响应协议定义internal/dataprovider/dataprovider.goSFTP 键盘交互认证的注册与回调internal/sftpd/server.go全局配置示例sftpgo.json。赞分享后端网络存储【免费下载链接】sftpgoFull-featured and highly configurable SFTP, HTTP/S, FTP/S and WebDAV server - S3, Google Cloud Storage, Azure Blob项目地址https://gitcode.com/gh_mirrors/sf/sftpgo点击查看免费下载相关推荐gopass 安全 OTP 存储实战共享密码库、本地分库与硬件令牌三种威胁模型下的双因素认证方案gopass 安全 OTP 存储实战共享密码库、本地分库与硬件令牌三种威胁模型下的双因素认证方案 导读 本指南围绕 gopass 中“安全 OTP一次性密码应用安全开发工具Google Authenticator 双因素认证如何确保一次性密码的绝对安全性Google Authenticator 双因素认证如何确保一次性密码的绝对安全性 Google Authenticator 是一款由谷歌开发的开源双因素认证网络安全应用安全Zabbix 前端集成 Google2FA基于 pragmarx/google2fa 的 TOTP 双因素认证实战指南Zabbix 前端集成 Google2FA基于 pragmarx/google2fa 的 TOTP 双因素认证实战指南 Google2FA 是一个面向 PHP指标监控可观测性告警运维上一篇VcXsrv Windows X Server如何在Windows上无缝运行Linux GUI应用下一篇终极免费跨平台绘图工具draw.io桌面版完整使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考