1. 引言

远程登录是 Linux 服务器运维的入口。默认的 SSH 配置在开发环境没什么毛病,但在生产环境中存在两个问题:一是密码认证易受暴力破解和窃听威胁;二是每次登录输密码在批量操作时效率极低。解决这两者的做法通常是启用公钥认证实现免密登录,同时收紧服务端的安全参数。

本文操作基于两台 Rocky Linux 9.5 主机(OpenSSH 8.7p1),使用普通用户 test 进行日常操作。如果系统中尚无该用户,需先以 root 身份在两台主机上分别创建并设置密码:

useradd test
passwd test

拓扑关系如下:

ssh

角色 主机名 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 中的哈希值比对。这个过程存在三个固有缺陷:

2.2 公钥认证的流程与优势

公钥认证基于非对称加密:客户端持有私钥(~/.ssh/id_ed25519),服务端存储对应的公钥(~/.ssh/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 目录,它会自动创建并设置权限为 700authorized_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

说明一下这些操作:

手动部署时务必检查上述两步的权限。~/.ssh 目录不能是 775777authorized_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.confPermitRootLogin 重新设为 yes,抵消了主文件中的 no。解决方式有两种:

修改完成后再次执行 sshd -tsystemctl 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 是自定义别名,HostNamePortUser 按实际连接参数填写。私钥使用默认的 id_ed25519,SSH 会自动识别,无需额外指定。如果有多个服务端,可以在同一配置文件中追加多个 Host 段落。

5. 实践:密钥转发与跳板登录

在实际生产环境中,目标服务器常常位于内网,不能直接从办公网访问。这时需要一台同时拥有公网 IP 和内网 IP 的跳板机,客户端先登录跳板机,再由跳板机连接内网目标机。这一节将使用 VMware 虚拟机的多网卡特性来模拟这一场景。

拓扑如下:

jump

角色 主机名 操作系统 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_SOCKSSH_AGENT_PID 两个环境变量导入当前 Shell——后续 sshssh-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 sshdsshd -T 的输出作为配置生效的证据链,避免盲目重启或回滚。对于多级网络环境,ssh-agent 配合 ProxyJump 可以安全地免密穿透跳板机,同时避免了私钥在中间节点落地。