1. 引言
远程登录是 Linux 服务器运维的入口。默认的 SSH 配置在开发环境没什么毛病,但在生产环境中存在两个问题:一是密码认证易受暴力破解和窃听威胁;二是每次登录输密码在批量操作时效率极低。解决这两者的做法通常是启用公钥认证实现免密登录,同时收紧服务端的安全参数。
本文操作基于两台 Rocky Linux 9.5 主机(OpenSSH 8.7p1),使用普通用户 test 进行日常操作。如果系统中尚无该用户,需先以 root 身份在两台主机上分别创建并设置密码:
useradd test
passwd test
拓扑关系如下:

| 角色 | 主机名 | IP 地址 | 操作系统 | OpenSSH 版本 |
|---|---|---|---|---|
| 客户端 | client | 192.168.92.128 | Rocky Linux 9.5 | 8.7p1 |
| 服务端 | server | 192.168.92.129 | Rocky Linux 9.5 | 8.7p1 |
文中所有涉及服务端配置修改、服务重载等需要 root 权限的操作,均需具有 root 权限(可通过 sudo 提权,或 su - 切换,也可直接以 root 登录)。普通用户 test 用于日常登录验证。
2. 认证机制的差异:密码与公钥
SSH 连接建立的本质是客户端与服务端就会话密钥达成一致,随后进行用户认证。认证阶段决定了最终能否拿到 Shell。
2.1 密码认证的流程与短板
密码认证时,服务端将加密后的密码与 /etc/shadow 中的哈希值比对。这个过程存在三个固有缺陷:
- 传输风险:尽管密码在 SSH 加密隧道中传输,但隧道密钥协商在先。如果服务端被中间人攻击(首次连接未验证主机密钥),密码仍可能在协商阶段被劫持。
- 暴力破解敞口:密码认证意味着服务端必须开放一个持续接收密码并比对的端口。攻击者可以穷举弱密码,即便有
MaxAuthTries限制,分布式慢速攻击仍然难以完全防御。 - 无状态可撤销性:密码泄露后需要到每台服务器上修改,无法像公钥那样在客户端单点吊销。
2.2 公钥认证的流程与优势
公钥认证基于非对称加密:客户端持有私钥(~/.ssh/id_ed25519),服务端存储对应的公钥(~/.ssh/authorized_keys)。连接时,服务端用公钥加密一个随机挑战值,客户端用私钥解密后返回,服务端验证通过即允许登录。
这个设计的优势在于:
- 私钥永不离线:私钥仅在客户端内存中解密使用,网络中传输的是签名结果,且每次签名包含会话 ID,无法重放。
- 可带 passphrase 加密:私钥文件本身用对称加密存储,即使文件被拷贝,没有 passphrase 也无法使用。
- 细粒度撤销:从
authorized_keys中删除对应公钥行即可立即失效,无需更改系统密码。
3. 实践:创建密钥对并完成免密登录
此节在客户端以 test 用户操作,目标是将公钥部署到服务端,使 test@client 能免密登录 test@server。
3.1 在客户端生成密钥对
生成密钥对前确认客户端已安装 OpenSSH 客户端:
[test@client ~]$ ssh -V
OpenSSH_8.7p1, OpenSSL 3.5.1 1 Jul 2025
使用 ssh-keygen 生成 Ed25519 算法密钥对。Ed25519 相比 RSA 2048 在相同安全强度下密钥更短、签名速度更快,且对侧信道攻击有更好抵抗性。
主线操作(不带 passphrase,适用于自动化场景):
ssh-keygen -t ed25519 -C "test@client - 2026-06"
[test@client ~]$ ssh-keygen -t ed25519 -C "test@client - 2026-06"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/test/.ssh/id_ed25519):
Created directory '/home/test/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/test/.ssh/id_ed25519
Your public key has been saved in /home/test/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:uFoYKbiHODe51nTN9M13lmLiBjnodnEUuWJDSQeAfxU test@client - 2026-06
The key's randomart image is:
...
带 passphrase 的备选路径(适用于人工日常登录):
如果希望在私钥文件被盗后仍有一层保护,可以在 ssh-keygen 提示 Enter passphrase 时输入一个密码短语:
ssh-keygen -t ed25519 -C "test@client - 2026-06"
[test@client ~]$ ssh-keygen -t ed25519 -C "test@client - 2026-06"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/test/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/test/.ssh/id_ed25519
Your public key has been saved in /home/test/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:Z4SbNlo3vzCkJdzdeoU6Dlp6nmiNsRLjDxtQTrUm1hc test@client - 2026-06
The key's randomart image is:
...
带 passphrase 的私钥在每次使用时需要先解密。下面的 3.3 节会展示连接时提示输入 passphrase 的过程。如果不希望每次手动输入,可以在 5.2 节配合 ssh-agent 一次性加载到内存中。
生成后检查文件权限:
[test@client ~]$ ls -la ~/.ssh/id_ed25519*
-rw-------. 1 test test 464 Jun 20 12:20 /home/test/.ssh/id_ed25519
-rw-r--r--. 1 test test 103 Jun 20 12:20 /home/test/.ssh/id_ed25519.pub
私钥权限必须是 600(仅所有者读写),否则 SSH 客户端会拒绝使用。
3.2 将公钥部署到服务端
公钥需追加到服务端 test 用户的 ~/.ssh/authorized_keys 中。
标准方式:使用 ssh-copy-id
ssh-copy-id 是最安全的方式——它会自动处理目录创建和权限设置,且不会覆盖已有公钥。
ssh-copy-id [email protected]
[test@client ~]$ ssh-copy-id [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/test/.ssh/id_ed25519.pub"
The authenticity of host '192.168.92.129 (192.168.92.129)' can't be established.
ED25519 key fingerprint is SHA256:IelcDuX5IknxbVcBwjTM3tLwrA7a9r2Ggz/x0IMUQSw.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.
ssh-copy-id 会要求输入服务端 test 的密码(首次部署时)。若服务端没有 ~/.ssh 目录,它会自动创建并设置权限为 700,authorized_keys 权限为 600。
备用方式:手动复制公钥
如果目标环境没有 ssh-copy-id 命令(比如某些最小化安装的系统),或者服务端 SSH 端口不通但能通过其他方式(如带外管理、云控制台 VNC)登录,可以手动完成部署。
先在客户端查看并复制公钥内容
[test@client ~]$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOZdM138WWdiZ3iOXpuO/wqHiOfo2s/euTnSF8GDQfdf test@client - 2026-06
选中并复制整行输出。
接着,在服务端创建目录并写入公钥
以 test 用户登录服务端,执行以下操作:
[test@server ~]$ mkdir -p ~/.ssh
[test@server ~]$ chmod 700 ~/.ssh
[test@server ~]$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOZdM138WWdiZ3iOXpuO/wqHiOfo2s/euTnSF8GDQfdf test@client - 2026-06' >> ~/.ssh/authorized_keys
[test@server ~]$ chmod 600 ~/.ssh/authorized_keys
说明一下这些操作:
mkdir -p ~/.ssh:创建.ssh目录,-p确保父目录存在时不报错;chmod 700 ~/.ssh:目录权限设为仅所有者可读写执行,SSH 会检查此项,权限过宽会导致忽略该目录;echo '...' >> ~/.ssh/authorized_keys:将公钥追加到授权文件末尾,使用>>避免覆盖已有公钥;chmod 600 ~/.ssh/authorized_keys:文件权限设为仅所有者可读写,SSH 同样会严格检查。
手动部署时务必检查上述两步的权限。
~/.ssh目录不能是775或777,authorized_keys不能是644或更高,否则 SSH 守护进程会拒绝读取该文件,表现为公钥配置“不生效”但仍然提示输密码。
3.3 验证免密登录是否生效
以下分两种情况验证:
情况一:私钥不带 passphrase
从客户端发起连接,此时不会再提示输入密码:
[test@client ~]$ ssh -o PreferredAuthentications=publickey [email protected]
Last login: Sat Jun 20 09:58:55 2026
[test@server ~]$
参数 -o PreferredAuthentications=publickey 强制客户端只尝试公钥认证,避免回退到密码干扰验证结果。登录后执行 exit 退出。
[test@server ~]$ exit
logout
Connection to 192.168.92.129 closed.
[test@client ~]$
情况二:私钥带 passphrase
如果生成时设置了 passphrase,首次连接会提示输入 passphrase 以解锁私钥:
[test@client ~]$ ssh -o PreferredAuthentications=publickey [email protected]
Enter passphrase for key '/home/test/.ssh/id_ed25519':
Last login: Sat Jun 20 11:28:37 2026 from 192.168.92.128
[test@server ~]$
此时输入正确的 passphrase 即可登录。这个提示来自 SSH 客户端调用本地私钥解密的过程,与服务端的认证配置无关。
4. 实践:生产环境服务端安全加固
免密登录建立后,需要在服务端修改 SSH 守护进程配置。本小节所有操作均需以 root 身份执行(可通过 sudo 提权,或 su - 切换,也可直接以 root 登录)。修改前务必确认当前已有一个保持连接的活动会话,避免配置错误导致断连后无法重入。
查看当前所有生效配置(以 root 执行):
[root@server ~]# sshd -T
port 22
permitrootlogin yes
passwordauthentication yes
pubkeyauthentication yes
permitemptypasswords no
...
接下来逐项调整 /etc/ssh/sshd_config。
4.1 调整认证与访问控制参数
使用 vi /etc/ssh/sshd_config(以 root 身份),修改或新增以下行:
# 禁用 root 直接登录
PermitRootLogin no
# 强制公钥认证,关闭密码认证(注意:确认公钥已生效后再执行)
PasswordAuthentication no
PubkeyAuthentication yes
# 禁用空密码账户
PermitEmptyPasswords no
# 限制认证尝试次数,防止暴力试探
MaxAuthTries 3
# 限制登录会话数量(防止资源耗尽)
MaxSessions 10
# 指定允许登录的用户(白名单)
AllowUsers test
PasswordAuthentication no是这次加固中最不可逆的操作。在执行此步前,必须已经在另一终端窗口中完成公钥登录验证,确保自己有除密码外的合法登录方式。如果只开了一个 SSH 会话,建议先开第二个窗口用公钥登录成功并保持,再修改此参数。
MaxAuthTries 默认值为 6,调低到 3 可减少单 IP 暴力破解的尝试次数,但不会完全阻止分布式攻击。AllowUsers test 显式列举可登录用户。
4.2 调整网络与会话参数
继续在 sshd_config 中添加:
# 更改监听端口(可减轻自动化扫描流量)
Port 2222
# 空闲超时断开(单位:秒),避免悬挂会话占用资源
ClientAliveInterval 300
ClientAliveCountMax 2
# 禁用 .rhosts 等旧式信任机制
IgnoreRhosts yes
HostbasedAuthentication no
# 日志等级提升到 VERBOSE,记录公钥指纹便于审计
LogLevel VERBOSE
Port 从 22 改为 2222 可减少互联网上的随机扫描流量,但不会抵御定向攻击,且需通知所有客户端调整连接参数。ClientAliveInterval 300 表示每 300 秒发送一次保活探测,如果连续 2 次无响应(ClientAliveCountMax 2)则断开会话——总超时约 10 分钟,适合长任务同时避免无限悬挂。
4.3 重新加载配置并验证生效状态
修改完成后,先使用 sshd -t 做语法检查,再重新加载(均以 root 执行):
[root@server ~]# sshd -t
[root@server ~]#
# 无输出表示语法通过
[root@server ~]# systemctl reload sshd
[root@server ~]#
# 无输出表示重载成功
检查端口变更是否生效:
[root@server ~]# ss -tlnp | grep sshd
LISTEN 0 128 0.0.0.0:2222 0.0.0.0:* users:(("sshd",pid=754,fd=3))
LISTEN 0 128 [::]:2222 [::]:* users:(("sshd",pid=754,fd=4))
[root@server ~]#
在客户端使用新端口重新连接,并验证密码认证已被禁用:
[test@client ~]$ ssh -p 2222 -o PreferredAuthentications=password [email protected]
[email protected]: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
收到 Permission denied (publickey) 且未出现 password 认证方法,说明 PasswordAuthentication no 生效,连接被强制要求公钥。
再验证公钥仍可正常登录:
[test@client ~]$ ssh -p 2222 [email protected]
Last login: Sat Jun 20 10:17:41 2026 from 192.168.92.128
查看服务端日志确认认证方式(以 root 执行):
[root@server ~]# journalctl -u sshd -n 10 --no-pager
Jun 20 10:51:26 server sshd[1714]: Connection from 192.168.92.128 port 36788 on 192.168.92.129 port 2222 rdomain ""
Jun 20 10:51:27 server sshd[1714]: Accepted key ED25519 SHA256:uFoYKbiHODe51nTN9M13lmLiBjnodnEUuWJDSQeAfxU found at /home/test/.ssh/authorized_keys:1
Jun 20 10:51:27 server sshd[1714]: Postponed publickey for test from 192.168.92.128 port 36788 ssh2 [preauth]
Jun 20 10:51:27 server sshd[1714]: Accepted key ED25519 SHA256:uFoYKbiHODe51nTN9M13lmLiBjnodnEUuWJDSQeAfxU found at /home/test/.ssh/authorized_keys:1
Jun 20 10:51:27 server sshd[1714]: Accepted publickey for test from 192.168.92.128 port 36788 ssh2: ED25519 SHA256:uFoYKbiHODe51nTN9M13lmLiBjnodnEUuWJDSQeAfxU
Jun 20 10:51:27 server sshd[1714]: pam_unix(sshd:session): session opened for user test(uid=1001) by test(uid=0)
Jun 20 10:51:27 server sshd[1714]: User child is on pid 1725
日志中的 Accepted publickey 与对应的指纹 SHA256 值,可用于后续审计追踪哪个密钥登录了系统。
4.4 确认配置未被 Include 覆盖
在较新版本的 OpenSSH 中,/etc/ssh/sshd_config 文件可能包含 Include 指令,会加载 /etc/ssh/sshd_config.d/ 目录下的碎片化配置文件。这些后续加载的文件中的相同参数会覆盖主文件中的设置,导致修改看似不生效。
检查主配置文件中是否有 Include 行:
[root@server ~]# grep -i "^Include" /etc/ssh/sshd_config
Include /etc/ssh/sshd_config.d/*.conf
如果存在,需要检查这些目录下是否有文件覆盖了刚才修改的参数:
[root@server ~]# grep -ri "PasswordAuthentication\|PermitRootLogin\|Port" /etc/ssh/sshd_config.d/
/etc/ssh/sshd_config.d/01-permitrootlogin.conf:PermitRootLogin yes
上例中 01-permitrootlogin.conf 将 PermitRootLogin 重新设为 yes,抵消了主文件中的 no。解决方式有两种:
- 将覆盖参数的文件中的对应行删除或注释掉;
- 在该文件中将参数改为与主文件一致的值。
修改完成后再次执行 sshd -t 和 systemctl reload sshd,并用 sshd -T | grep -E "passwordauthentication|permitrootlogin|port" 确认最终生效值。
sshd -T 输出的是所有配置合并后的最终结果,验证时应以此为准,而不是只看主配置文件。
4.5 在客户端配置主机别名
加固完成后,服务端已使用 2222 端口。为了简化日常连接命令,可以在客户端创建 ~/.ssh/config 文件,为服务端定义一个别名。
[test@client ~]$ mkdir -p ~/.ssh
[test@client ~]$ chmod 700 ~/.ssh
[test@client ~]$ cat > ~/.ssh/config << 'EOF'
Host prod
HostName 192.168.92.129
Port 2222
User test
EOF
[test@client ~]$ chmod 600 ~/.ssh/config
~/.ssh/config 的权限同样需要收紧(600),否则 SSH 客户端会忽略该文件。
配置完成后,可以用 ssh prod 替代完整命令:
[test@client ~]$ ssh prod
Last login: Sat Jun 20 13:44:35 2026 from 192.168.92.128
[test@server ~]$
Host是自定义别名,HostName、Port、User按实际连接参数填写。私钥使用默认的id_ed25519,SSH 会自动识别,无需额外指定。如果有多个服务端,可以在同一配置文件中追加多个Host段落。
5. 实践:密钥转发与跳板登录
在实际生产环境中,目标服务器常常位于内网,不能直接从办公网访问。这时需要一台同时拥有公网 IP 和内网 IP 的跳板机,客户端先登录跳板机,再由跳板机连接内网目标机。这一节将使用 VMware 虚拟机的多网卡特性来模拟这一场景。
- NAT 网络(VMnet8):模拟公网,客户端通过该网络访问跳板机的“公网 IP”
- 仅主机网络(VMnet1):模拟内网,跳板机与目标机处于同一内网网段,客户端无法直接访问目标机
拓扑如下:

| 角色 | 主机名 | 操作系统 | VMware 网络模式 | IP 地址 / 掩码 |
|---|---|---|---|---|
| 客户端 | client | Ubuntu 24.04 | NAT(VMnet8) | 192.168.92.130/24 |
| 跳板机 | jump | Rocky Linux 9.5 | NAT(VMnet8) | 192.168.92.128/24 |
| 仅主机(VMnet1) | 192.168.30.129/24 | |||
| 服务端 | server | Rocky Linux 9.5 | 仅主机(VMnet1) | 192.168.30.130/24 |
注意:VMware 安装后会在 Windows 宿主机上创建 VMnet8(NAT)和 VMnet1(仅主机)两张虚拟网卡。宿主机同时接入这两个网络时,Windows 会自动执行跨网段路由转发,导致客户端(NAT 网络)能直接访问目标机(仅主机网络)——这与模拟“内外网隔离”的目标冲突。解决方法:Win + R,输入ncpa.cpl 回车查看网络连接,右键
VMware Network Adapter VMnet1,将其禁用。禁用后,VMnet1 仍存在于虚拟机内部,但宿主机不再参与该网段的二层转发,客户端无法跨越 VMnet8 到达目标机。
5.1 网络环境要求
验证客户端到跳板机的连通性:
root@client:~# ping -c 2 192.168.92.128
PING 192.168.92.128 (192.168.92.128) 56(84) bytes of data.
64 bytes from 192.168.92.128: icmp_seq=1 ttl=64 time=0.363 ms
64 bytes from 192.168.92.128: icmp_seq=2 ttl=64 time=0.524 ms
--- 192.168.92.128 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1056ms
rtt min/avg/max/mdev = 0.363/0.443/0.524/0.080 ms
验证客户端到目标机的连通性(预期不通,因为目标机在仅主机网络,客户端在 NAT 网络,两者隔离):
root@client:~# ping -c 2 192.168.30.130
PING 192.168.30.130 (192.168.30.130) 56(84) bytes of data.
--- 192.168.30.130 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1053ms
验证跳板机到目标机的连通性:
[root@jump ~]# ping -c 2 192.168.30.130
PING 192.168.30.130 (192.168.30.130) 56(84) bytes of data.
64 bytes from 192.168.30.130: icmp_seq=1 ttl=64 time=0.648 ms
64 bytes from 192.168.30.130: icmp_seq=2 ttl=64 time=0.653 ms
--- 192.168.30.130 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1058ms
rtt min/avg/max/mdev = 0.648/0.650/0.653/0.002 ms
5.2 在各主机上部署公钥
在客户端生成密钥对
root@client:~# ssh-keygen -t ed25519 -C "root@client - 2026-06"
root@client:~# ssh-keygen -t ed25519 -C "root@client - 2026-06"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_ed25519
Your public key has been saved in /root/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:KslhW9gZ5NWfQkYITHdvseW2uPIt50EL3AtbWUyg22g root@client - 2026-06
The key's randomart image is:
...
查看并复制公钥内容:
root@client:~# cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQ1t5zTj/XrnBCiIZuKB4jijc7p8/8DzGc+zoa1qAgS root@client - 2026-06
将公钥部署到跳板机
客户端通过跳板机的 NAT IP(192.168.92.128)部署公钥:
root@client:~# ssh-copy-id [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.
验证免密登录跳板机:
root@client:~# ssh -o PreferredAuthentications=publickey [email protected]
Enter passphrase for key '/root/.ssh/id_ed25519':
Last login: Sat Jun 20 13:43:27 2026
[test@jump ~]$
将客户端的公钥部署到目标机
目标机只有内网 IP(192.168.30.130),客户端无法直接访问。需要通过跳板机将公钥传过去。
方式一:跳板机本地生成密钥对并推送到目标机
跳板机自己持有一对密钥,用它来免密登录目标机。
(1)检查跳板机是否已有密钥对
[test@jump ~]$ ls -la ~/.ssh/id_*.pub
ls: cannot access '/home/test/.ssh/id_*.pub': No such file or directory
没有密钥对,需要生成:
[test@jump ~]$ ssh-keygen -t ed25519 -C "test@jump - 2026-06"
[test@jump ~]$ ssh-keygen -t ed25519 -C "test@jump - 2026-06"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/test/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/test/.ssh/id_ed25519
Your public key has been saved in /home/test/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:zK+qP06W2eRO3cCa0nG32yzI0jbBUNhFfLA/lOG8IAg test@jump - 2026-06
The key's randomart image is:
...
(2)将跳板机的公钥推送到目标机
ssh-copy-id [email protected]
[test@jump ~]$ ssh-copy-id [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/test/.ssh/id_ed25519.pub"
The authenticity of host '192.168.30.130 (192.168.30.130)' can't be established.
ED25519 key fingerprint is SHA256:zK+qP06W2eRO3cCa0nG32yzI0jbBUNhFfLA/lOG8IAg.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.
输入目标机 test 用户的密码后,跳板机的公钥就被追加到了目标机的 ~/.ssh/authorized_keys 中。
(3)验证跳板机到目标机免密登录
[test@jump ~]$ ssh -o PreferredAuthentications=publickey [email protected]
Enter passphrase for key '/home/test/.ssh/id_ed25519':
Last login: Sat Jun 20 14:25:44 2026 from 192.168.92.128
[test@server ~]$
此时跳板机能免密登录目标机。但客户端还不能直接登录目标机,客户端需要先登录跳板机,再由跳板机连接目标机,这就是跳板登录的场景。
方式二:手动复制客户端公钥到目标机
如果目标机禁止密码登录,或 ssh-copy-id 不可用,可以手动操作。
(1)从跳板机登录目标机
[test@jump ~]$ ssh [email protected]
Enter passphrase for key '/home/test/.ssh/id_ed25519':
Last login: Sat Jun 20 16:14:56 2026 from 192.168.30.129
[test@server ~]$
(2)在目标机上手动追加客户端公钥
先退出到客户端,查看公钥内容:
[test@server ~]$ exit
logout
Connection to 192.168.30.130 closed.
[test@jump ~]$ exit
logout
Connection to 192.168.92.128 closed.
root@client:~# cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQ1t5zTj/XrnBCiIZuKB4jijc7p8/8DzGc+zoa1qAgS root@client - 2026-06
root@client:~#
重新登录跳板机,再从跳板机登录目标机,在目标机上手动追加:
[test@server ~]$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQ1t5zTj/XrnBCiIZuKB4jijc7p8/8DzGc+zoa1qAgS root@client - 2026-06' >> ~/.ssh/authorized_keys
[test@server ~]$ chmod 600 ~/.ssh/authorized_keys
[test@server ~]$ exit
logout
Connection to 192.168.30.130 closed.
[test@jump ~]$ exit
logout
Connection to 192.168.92.128 closed.
root@client:~#
从客户端验证能否通过跳板机登录目标机
root@client:~# ssh -J [email protected] [email protected]
Enter passphrase for key '/root/.ssh/id_ed25519':
Last login: Sat Jun 20 16:47:58 2026 from 192.168.30.129
[test@server ~]$
方式三:通过 ssh -A 转发客户端密钥
使用 ssh -A 将客户端的认证代理转发到跳板机,使跳板机能调用客户端的私钥来认证目标机。
(1)在客户端启动 ssh-agent 并添加私钥
root@client:~# eval $(ssh-agent)
Agent pid 33808
root@client:~# ssh-add ~/.ssh/id_ed25519
Enter passphrase for /root/.ssh/id_ed25519:
Identity added: /root/.ssh/id_ed25519 (root@client - 2026-06)
(2)通过 -A 登录跳板机,再从跳板机将客户端的公钥推送到目标机
root@client:~# ssh -A [email protected]
[test@jump ~]$ ssh-copy-id [email protected]
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.
[test@jump ~]$
ssh -A让跳板机能调用客户端的私钥去尝试认证目标机。如果目标机上尚未安装该公钥,公钥认证会失败并回退到密码认证——这是第一次部署时的正常现象。输入密码后公钥被安装,后续连接不再需要密码。只有在目标机上已经存在该公钥的情况下,ssh-copy-id才会完全跳过密码。
(3)从客户端验证能否通过跳板机登录目标机
root@client:~# ssh -J [email protected] [email protected]
Last login: Sat Jun 20 16:52:38 2026 from 192.168.30.129
[test@server ~]$
-J是 OpenSSH 7.3 引入的ProxyJump参数,作用是指定跳板机作为中间节点,将 SSH 连接通过跳板机转发到目标机。执行上述命令时,SSH 客户端会先与192.168.92.128建立连接,再通过该连接转发到192.168.30.130,效果等同于ssh -o ProxyCommand="ssh -W %h:%p [email protected]" [email protected],但语法更简洁。
三种方式的对比:
| 方式 | 目标机 authorized_keys 里存的是谁的公钥 |
跳板机上是否有私钥 | 私钥是否落地 |
|---|---|---|---|
| 方式一 | 跳板机的公钥 | 有(跳板机自己的私钥) | 跳板机上有私钥文件 |
| 方式二 | 客户端的公钥 | 没有客户端私钥(操作时用密码登录目标机) | 客户端私钥不离开客户端 |
| 方式三 | 客户端的公钥 | 没有(通过 -A 转发认证) |
客户端私钥不离开客户端 |
最好选择方式三,客户端的私钥全程不落地,是跳板机安全场景的最佳实践。
5.3 使用 ssh-agent 避免重复输入 passphrase
如果私钥设置了 passphrase,每次使用 ssh 时都需要输入。ssh-agent 可以将私钥一次性加载到内存中,后续连接自动使用。
在客户端启动 ssh-agent 并添加私钥:
root@client:~# eval $(ssh-agent)
Agent pid 37396
root@client:~# ssh-add ~/.ssh/id_ed25519
Enter passphrase for /root/.ssh/id_ed25519:
Identity added: /root/.ssh/id_ed25519 (root@client - 2026-06)
ssh-agent是一个运行在内存中的密钥管理器,负责保管已解密的私钥。eval $(ssh-agent)的作用是启动ssh-agent进程,并将SSH_AUTH_SOCK和SSH_AGENT_PID两个环境变量导入当前 Shell——后续ssh和ssh-add命令会读取这两个变量来找到ssh-agent通信 socket。直接执行ssh-agent而不加eval虽然也能启动进程,但环境变量不会自动导入 Shell,当前会话中的 SSH 客户端无法找到 agent。ssh-add将私钥解密后加载到ssh-agent中,此后在当前会话中任何用到该私钥的 SSH 连接都会自动从这个 agent 获取,无需重复输入 passphrase。
查看已加载的密钥:
root@client:~# ssh-add -l
256 SHA256:KslhW9gZ5NWfQkYITHdvseW2uPIt50EL3AtbWUyg22g root@client - 2026-06 (ED25519)
在客户端配置文件中启用密钥转发:
在客户端的 ~/.ssh/config 中添加:
root@client:~# cat > ~/.ssh/config << 'EOF'
Host jump
HostName 192.168.92.128
User test
ForwardAgent yes
Host server
HostName 192.168.30.130
User test
ProxyJump jump
ForwardAgent yes
EOF
root@client:~# chmod 600 ~/.ssh/config
ForwardAgent yes的作用是将客户端的ssh-agent通信 socket 通过 SSH 连接转发到跳板机上,使跳板机上的 SSH 客户端能够向客户端的 agent 请求签名操作。当跳板机连接目标机时,它不需要本地私钥文件——它会把认证请求发回客户端 agent,由客户端完成私钥签名后将结果返回跳板机。这种机制的关键是:私钥始终在客户端内存中,跳板机上没有私钥文件。但需要注意,ForwardAgent yes也意味着跳板机上的 root 用户可以通过 socket 使用你的私钥发起其他连接,因此只应在信任的跳板机上启用,不用时在配置中移除该选项或改为ForwardAgent no。
之后只需一条命令即可直达目标机,全程免密:
root@client:~# ssh server
Last login: Sat Jun 20 17:03:04 2026 from 192.168.30.129
[test@server ~]$
5.4 验证密钥转发链路
登录目标机后,检查 SSH 认证代理的转发状态:
[test@server ~]$ echo $SSH_AUTH_SOCK
/tmp/ssh-XXXX9H8SY9/agent.1909
[test@server ~]$ ssh-add -l
256 SHA256:KslhW9gZ5NWfQkYITHdvseW2uPIt50EL3AtbWUyg22g root@client - 2026-06 (ED25519)
ssh-add -l 列出的指纹与客户端私钥的指纹一致,这说明私钥已成功通过跳板机转发到目标机。
结语
公钥认证配合服务端参数收紧,将 SSH 登录从“密码 + 开放端口”的默认态转变为“密钥 + 最小暴露面”的生产态。两种认证方式的切换不是简单的开关操作——PasswordAuthentication no 的生效依赖前置的权限、标签和验证闭环,任何一个环节断裂都会导致不可登录。在日常运维中可借助 journalctl -u sshd 和 sshd -T 的输出作为配置生效的证据链,避免盲目重启或回滚。对于多级网络环境,ssh-agent 配合 ProxyJump 可以安全地免密穿透跳板机,同时避免了私钥在中间节点落地。