命令行自动登录 JumpServer:SSH、TOTP 与密码提示的自动化
2026-6-5
| 2026-6-5
字数 1752阅读时长 5 分钟
type
Post
status
Published
date
Jun 5, 2026
slug
jumpserver-cli-auto-login
summary
把 JumpServer 登录流程拆成凭据读取和终端交互两层:zsh 从密码管理器安全读取 TOTP seed 与可选密码,expect 只负责响应 SSH 登录过程中的 password 和 Code 提示。
tags
SSH
JumpServer
zsh
Bitwarden
expect
category
技术分享
icon
password
AI summary
Last edited time
Jun 5, 2026 02:26 AM
JumpServer 的命令行登录通常不只是一次 ssh:先走 SSH 公钥或密码认证,再输入 MFA 动态验证码,某些环境还会额外进入 password prompt。每次手动复制验证码既慢,也容易在多个跳板机之间输错。
我的处理方式是把流程拆成两层:
  • zsh 负责准备凭据:检查依赖、解锁密码库、读取 TOTP seed、必要时读取 SSH password。
  • expect 负责终端交互:启动 ssh,遇到 password prompt 就发送密码,遇到 MFA Code prompt 就生成并发送当前 TOTP,最后把终端控制权交还给用户。
这样做的好处是:敏感信息不写进脚本,交互逻辑也不会和密码管理器逻辑混在一起。

前置条件

这套方案依赖以下工具:
还需要安装并登录 Bitwarden CLI:
如果使用 Bitwarden SSH Agent,还要确保 SSH 配置里显式指定 agent 和 key,避免 agent 里多把 key 被逐个尝试,提前触发服务端的 MaxAuthTries
注意:这里的 <jumpserver-host><ssh-user><jumpserver-key> 都应该替换成你自己的环境值,不要把真实地址和账号提交到公开仓库。

命令入口

最终希望日常只执行一个函数:
这个入口有三个参数含义:
  • <ssh-host-alias>:本机 ~/.ssh/config 里的 Host 别名。
  • <bitwarden-item-name>:Bitwarden 中保存 JumpServer 登录信息的 item 名。
  • -bw-password:表示 SSH password 也从 Bitwarden item 中读取;如果目标环境只需要公钥和 MFA,可以不传。

zsh 负责读取凭据

核心函数先检查依赖,再处理 Bitwarden 状态:
这里有几个关键点:
  • bw sync --force:避免桌面端刚更新了密码,但 CLI 还读到旧缓存。
  • _bw_get_exact_item:校验返回的 item name 必须完全等于请求值,避免 Bitwarden 的模糊匹配拿错条目。
  • _bw_totp_secret_from_json:读取 .login.totp,同时兼容 otpauth://...?secret=... 这种格式。
  • _bw_password_from_json:优先读 .login.password,也可以兼容自定义字段。

避免 Bitwarden 模糊匹配

bw get item <name> 在某些情况下可能返回相近名称的条目。对 JumpServer 来说,这很危险:拿错 TOTP seed 会生成错误验证码,而且错误不一定直观。
因此我会加一层精确校验:

从 Bitwarden 解析 TOTP seed

Bitwarden 的 TOTP 字段可能是纯 base32 secret,也可能是 otpauth:// URL。下面的 helper 会统一转成 oathtool 可用的 base32 seed:
脚本里不要保存当前 6 位验证码。验证码是短时值,应该在 expect 收到 Code prompt 的那一刻再生成,避免过期。

expect 只处理 SSH 交互

expect 部分保持简单:只做终端自动输入,不直接访问 Bitwarden。
这里不要用 expect -f - 直接喂 heredoc。那样 expect 的 stdin 会被脚本内容占用,后面的 interact 可能无法继续接管当前终端,表现为发送验证码后会话直接退出。

password 的处理

有些 JumpServer 环境会先走公钥,再进入 SSH password prompt。不要把 password 硬编码在 .zshrc 里,可以从 Bitwarden item 的 .login.password 或自定义字段读取:

常见问题

too many authentication failures

如果 Bitwarden SSH Agent 里有多把 key,OpenSSH 默认可能逐个尝试。服务端在正确 key 被尝试之前就可能达到 MaxAuthTries,返回 too many authentication failures
解决方法是在对应 Host 中配置:

Code prompt 匹配不到

不同 JumpServer 版本的 MFA 提示文字可能不同。可以先手动执行一次 ssh <host-alias>,观察提示文本,再调整 expect 分支里的 pattern。
例如:
如果你的提示是 Verification code:,就改成对应 pattern。

发送验证码后会话退出

优先检查是否使用了 expect -f - 加 heredoc。更稳的做法是写入临时 expect 脚本文件,再用 expect -f "$expect_script" 执行,最后 rm -f 清理。

安全边界

这套方案的原则是:
  • .zshrc 只保存函数和占位式配置,不保存密码、TOTP seed、真实主机信息。
  • TOTP seed 和 password 只从密码管理器读取。
  • expect 不直接调用密码管理器,减少交互层复杂度。
  • SSH config 使用 Host 别名,文章、脚本示例和公开文档只展示别名和占位符。
  • 调试日志不要上传包含 HostName、User、公钥指纹、验证码、密码库 item 名的原始输出。

最终使用

配置完成后,日常登录就变成:
命令会自动完成 Bitwarden 同步、TOTP seed 读取、可选 password 读取和 JumpServer MFA 输入。成功后会进入正常 SSH 会话,后续操作仍然由当前终端接管。
  • SSH
  • JumpServer
  • zsh
  • Bitwarden
  • expect
  • Grafana 配置手册git 个人开发工作流
    Loading...