You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Apache SSHD通过网关隧道连接node1服务器失败求助

问题分析与解决方案

你的报错核心是客户端不支持服务器(node1)使用的ssh-ed25519主机密钥算法,而非密钥交换(KeyExchange)算法不匹配——你之前调整KeyExchangeFactories的方向错了。

报错信息明确显示:

Error: Unable to negotiate key exchange for server host key algorithms (client: ... / server: ssh-ed25519)

注意这里的server host key algorithms是指服务器用于身份验证的主机密钥类型,不是密钥交换算法。你的Apache SSHD客户端默认没启用ed25519主机密钥支持,导致无法和只支持ssh-ed25519的node1协商。

解决步骤

1. 启用ed25519主机密钥支持

Apache SSHD 2.11.0已经支持ed25519,但默认未将其加入启用的主机密钥算法列表,需要显式配置。

2. 修改客户端配置

在初始化SshClient时,添加ed25519相关配置,替换你原本的KeyExchangeFactories配置部分:

SshClient client = SshClient.setUpDefaultClient();
// 启用ed25519主机密钥算法,同时保留其他常用算法
client.setHostKeyAlgorithms("ssh-ed25519", "ecdsa-sha2-nistp256", "rsa-sha2-256", "ssh-rsa");
// 添加ed25519密钥提供者
client.setKeyPairProvider(KeyPairProvider.ED25519);

// 保留原有其他配置
client.setCompressionFactoriesNames(BuiltinCompressions.Constants.NONE);
client.setCipherFactoriesNames("aes256-gcm@openssh.com","aes128-gcm@openssh.com","aes256-ctr","aes192-ctr","aes128-ctr");
client.setMacFactoriesNames("hmac-sha2-256-etm@openssh.com","hmac-sha2-512-etm@openssh.com","hmac-sha2-256","hmac-sha2-512");

3. 额外注意事项

  • 从网关获取的私钥需确保已被node1信任(即该私钥的公钥已添加到node1的~/.ssh/authorized_keys文件中)。
  • 如果node1使用的是ed25519类型私钥,当前版本的sshd-core已支持加载,无需额外依赖。

修改后的完整代码示例

package org.example;

import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ChannelExec;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.compression.BuiltinCompressions;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
import org.apache.sshd.common.util.net.SshdSocketAddress;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.nio.file.Files;
import java.security.KeyPair;
import java.util.EnumSet;
import java.util.List;
import java.util.ArrayList;
import java.util.stream.Collectors;

public class RemoteCommandExecutor {

    public static void main(String[] args) throws Exception {
        String gatewayHost = "host ip";
        String gatewayUser = "username";
        String gatewayPassword = "password";
        int localPort = 12345; // 选择未使用的端口
        int remotePort = 22; // node1的SSH端口

        String node1Host = "ixxxxxx017";
        String node1User = "username";
        String passphrase = "password";
        List<String> commands = List.of("hostname", "cd /home/", "ls -lrt");

        String privateKeyPath = "/home/"+gatewayUser+"/.ssh/id_rsa";

        try {
            SshClient client = SshClient.setUpDefaultClient();
            // 启用ed25519主机密钥算法
            client.setHostKeyAlgorithms("ssh-ed25519", "ecdsa-sha2-nistp256", "rsa-sha2-256", "ssh-rsa");
            // 添加ed25519密钥提供者
            client.setKeyPairProvider(KeyPairProvider.ED25519);
            
            // 原有配置
            client.setCompressionFactoriesNames(BuiltinCompressions.Constants.NONE);
            client.setCipherFactoriesNames("aes256-gcm@openssh.com","aes128-gcm@openssh.com","aes256-ctr","aes192-ctr","aes128-ctr");
            client.setMacFactoriesNames("hmac-sha2-256-etm@openssh.com","hmac-sha2-512-etm@openssh.com","hmac-sha2-256","hmac-sha2-512");

            client.start();
            // 连接网关
            try (ClientSession gatewaySession = client.connect(gatewayUser, gatewayHost, 22)
                    .verify().getSession()) {
                gatewaySession.addPasswordIdentity(gatewayPassword);
                gatewaySession.auth().verify();
                String testOutput = executeCommand(gatewaySession, "ls -l");
                System.out.println("网关测试输出:" + testOutput);
                
                // 建立本地端口转发
                gatewaySession.startLocalPortForwarding(
                        new SshdSocketAddress("localhost", localPort),
                        new SshdSocketAddress(node1Host, remotePort)
                );

                // 通过隧道连接node1
                try (ClientSession node1Session = client.connect(node1User, "localhost", localPort)
                        .verify().getSession()) {
                    // 从网关获取私钥内容
                    String privateKeyContents = executeCommand(gatewaySession, "cat " + privateKeyPath);
                    File tempPrivateKeyFile = File.createTempFile("temp-private-key", ".pem");
                    Files.write(tempPrivateKeyFile.toPath(), privateKeyContents.getBytes());

                    // 加载私钥
                    FileKeyPairProvider keyPairProvider = new FileKeyPairProvider(List.of(tempPrivateKeyFile.toPath()));
                    keyPairProvider.setPasswordFinder(FilePasswordProvider.of(passphrase));
                    Iterable<KeyPair> keyPairs = keyPairProvider.loadKeys(node1Session);
                    KeyPair keyPair = keyPairs.iterator().next();

                    node1Session.addPublicKeyIdentity(keyPair);
                    node1Session.auth().verify();

                    // 执行命令
                    List<String> results = new ArrayList<>();
                    for (String cmd : commands) {
                        String output = executeCommand(node1Session, cmd);
                        results.add(output);
                    }

                    String combinedOutput = results.stream().collect(Collectors.joining("\n---\n"));
                    System.out.println("node1命令输出:\n" + combinedOutput);
                }
            } finally {
                client.stop();
                client.close();
            }
        } catch (Exception e) {
            System.err.println("错误:" + e.getMessage());
            e.printStackTrace();
        }
    }

    private static String executeCommand(ClientSession session, String command) throws Exception {
        ChannelExec channelExec = session.createExecChannel(command);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayOutputStream err = new ByteArrayOutputStream();

        channelExec.setOut(out);
        channelExec.setErr(err);
        channelExec.open();
        channelExec.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0);

        String output = new String(out.toByteArray());
        String error = new String(err.toByteArray());

        if (!error.isEmpty() && !error.contains("cannot find name for group ID")) {
            throw new Exception("命令执行错误:" + error);
        }
        return output;
    }
}

Maven依赖(无需修改)

<dependencies>
    <dependency>
        <groupId>org.apache.sshd</groupId>
        <artifactId>sshd-core</artifactId>
        <version>2.11.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.sshd</groupId>
        <artifactId>sshd-sftp</artifactId>
        <version>2.11.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.sshd</groupId>
        <artifactId>sshd-common</artifactId>
        <version>2.11.0</version>
    </dependency>
</dependencies>

内容的提问来源于stack exchange,提问作者Ravi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 11:17:04