踩坑记录:java连接ssh的问题

发布时间:2023年12月18日

概述

手里有个CS架构的老系统,服务端要用SSH的方式传文件。没想到写了两天!遇到一堆问题,于是记录下。(老系统真恶心啊!)

一、第一个

问题

一开始使用原有的jsch包,但是ssh连不上,查资料后发现,服务器的ssh版本升级了,而且中央仓库的jsch包十几年不更新了,缺少很多密钥算法。

解决

改为使用apache的sshd包,我看一直有更新,因为线上服务器的ssh更高,于是用了比较新的版本。而且需要三个包,详细代码后面有。
sshd-core
sshd-sftp
sshd-common (这里和第二个坑有关!)

二、第二个

问题

使用sshd后,本地测试没问题,而放到测试服务器,服务端就一直报找不到sshd下面的ClientSession。

分析

因为是ClassLoader报的,原以为是二次代理,导致的ClassLoader无法获取第二次代理的外部jar(老架构的service全是用代理模式获取的,之前遇到过因二次代理导致service中无法获取hutool的问题)。后来想想这里不存在二次代理的问题,应该就只是ClassLoader获取外部jar包,于是找了下这个类,谁知。。(这里是idea用maven看的,netbean看起来实在麻烦)
在这里插入图片描述
core和common都有org.apache.sshd.client包!ClientSession在core里(不理解为啥不都装到core里!)
ClassLoader会先去找上面的common包找org.apache.sshd.client.ClientSession,找不到就不接着找了。

解决

用URLClassLoader去指定加载core里的ClientSession,但是想想太麻烦算了,于是准备把ssh传文件写成脚本,service去调用脚本的方式。

三、第三个

问题

开始写脚本,因为服务器没有go环境,python也有各种问题不想用,就用java写了个脚本。

import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.kex.DHGClient;
import org.apache.sshd.client.kex.DHGEXClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchangeFactory;
import org.apache.sshd.scp.client.ScpClient;
import org.apache.sshd.scp.client.ScpClientCreator;

import java.util.Arrays;

/**
 * ssh执行器
 * 
 * @author zilong
 * @date 2023/12/14
 **/
public class SshExcute {
    
    public static void main(String args[]) {
        String username = null;
        String password = null;
        String ip = null;
        String sourcePath = null;
        String targetPath = null;
        String fileName = null;
        if (args[0] != null) {
            username = args[0];
        }
        if (args[1] != null) {
            password = args[1];
        }
        if (args[2] != null) {
            ip = args[2];
        }
        if (args[3] != null) {
            sourcePath = args[3];
        }
        if (args[4] != null) {
            targetPath = args[4];
        }
        if (args[5] != null) {
            fileName = args[5];
        }
        sendFile(
                username,
                password,
                ip,
                sourcePath,
                targetPath,
                fileName
        );
    }
    
    public static boolean sendFile (
            String username, String password, String ip, String sourcePath,
            String targetPath, String fileName
    ) {
        Security.addProvider(new BouncyCastleProvider());
        int port = 22;
        try {
            sourcePath = sourcePath + "/";
            String osName = System.getProperty("os.name");
            if (osName.toUpperCase().contains("WINDOWS")) {
                sourcePath = sourcePath.replaceAll("/", "\\\\");
            }
            String source = sourcePath + fileName;
            String target = targetPath + "/" + fileName;
            SshClient client = SshClient.setUpDefaultClient();
            client.start();

            ClientSession session = client.connect(username, ip, port).verify().getSession();
            session.addPasswordIdentity(password);
            boolean isSuccess = session.auth().verify().isSuccess();

            if (isSuccess) {
                ScpClientCreator creator = ScpClientCreator.instance();
                ScpClient scpClient = creator.createScpClient(session);

                System.out.println("Scp beginning...");
                scpClient.upload(source, target, ScpClient.Option.Recursive);
                System.out.println("Scp finished...");

                if (scpClient != null) {
                    scpClient = null;
                }

                if (session != null && session.isOpen()) {
                    session.close();
                }
                if (client != null && client.isOpen()) {
                    client.stop();
                    client.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}
java.lang.IllegalArgumentException: KeyExchangeFactories not set
	at org.apache.sshd.common.util.ValidateUtils.createFormattedException(ValidateUtils.java:213)
	at org.apache.sshd.common.util.ValidateUtils.throwIllegalArgumentException(ValidateUtils.java:179)
	at org.apache.sshd.common.util.ValidateUtils.checkTrue(ValidateUtils.java:174)
	at org.apache.sshd.common.util.ValidateUtils.checkNotNullAndNotEmpty(ValidateUtils.java:80)
	at org.apache.sshd.common.helpers.AbstractFactoryManager.checkConfig(AbstractFactoryManager.java:513)
	at org.apache.sshd.client.SshClient.checkConfig(SshClient.java:389)
	at org.apache.sshd.client.SshClient.start(SshClient.java:450)
	at Ss.sendFile(Ss.java:81)
	at Ss.main(Ss.java:46)

分析

debug看源码
在这里插入图片描述本地测试是有这些密钥算法的,但是服务器上没有,不知道从哪获取的,手动加上试试

在这里插入图片描述又报错
java.security.NoSuchAlgorithmException: Algorithm ECDH not avaliable
感觉还是得从源头下手,应该是服务器缺少相关环境,于是百度,发现是jre缺少加密算法相关的包导致的,那么手动加下就好了。
The Bouncy Castle Crypto package

解决

import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.kex.DHGClient;
import org.apache.sshd.client.kex.DHGEXClient;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.kex.BuiltinDHFactories;
import org.apache.sshd.common.kex.KeyExchangeFactory;
import org.apache.sshd.scp.client.ScpClient;
import org.apache.sshd.scp.client.ScpClientCreator;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.Security;
import java.util.Arrays;

/**
 * ssh执行器
 * 
 * @author zilong
 * @date 2023/12/14
 **/
public class SshExcute {
    
    public static void main(String args[]) {
        String username = null;
        String password = null;
        String ip = null;
        String sourcePath = null;
        String targetPath = null;
        String fileName = null;
        if (args[0] != null) {
            username = args[0];
        }
        if (args[1] != null) {
            password = args[1];
        }
        if (args[2] != null) {
            ip = args[2];
        }
        if (args[3] != null) {
            sourcePath = args[3];
        }
        if (args[4] != null) {
            targetPath = args[4];
        }
        if (args[5] != null) {
            fileName = args[5];
        }
        sendFile(
                username,
                password,
                ip,
                sourcePath,
                targetPath,
                fileName
        );
    }
    
    public static boolean sendFile (
            String username, String password, String ip, String sourcePath,
            String targetPath, String fileName
    ) {
        Security.addProvider(new BouncyCastleProvider());
        int port = 22;
        try {
            sourcePath = sourcePath + "/";
            String osName = System.getProperty("os.name");
            if (osName.toUpperCase().contains("WINDOWS")) {
                sourcePath = sourcePath.replaceAll("/", "\\\\");
            }
            String source = sourcePath + fileName;
            String target = targetPath + "/" + fileName;
            SshClient client = SshClient.setUpDefaultClient();
            client.setKeyExchangeFactories(Arrays.<KeyExchangeFactory>asList(
                    DHGClient.newFactory(BuiltinDHFactories.ecdhp521),
                    DHGClient.newFactory(BuiltinDHFactories.ecdhp384),
                    DHGClient.newFactory(BuiltinDHFactories.ecdhp256),
                    DHGEXClient.newFactory(BuiltinDHFactories.dhgex256),
                    DHGClient.newFactory(BuiltinDHFactories.dhg18_512),
                    DHGClient.newFactory(BuiltinDHFactories.dhg17_512),
                    DHGClient.newFactory(BuiltinDHFactories.dhg16_512),
                    DHGClient.newFactory((BuiltinDHFactories.dhg15_512)),
                    DHGClient.newFactory(BuiltinDHFactories.dhg14_256)));
            client.start();

            ClientSession session = client.connect(username, ip, port).verify().getSession();
            session.addPasswordIdentity(password);
            boolean isSuccess = session.auth().verify().isSuccess();

            if (isSuccess) {
                ScpClientCreator creator = ScpClientCreator.instance();
                ScpClient scpClient = creator.createScpClient(session);

                System.out.println("Scp beginning...");
                scpClient.upload(source, target, ScpClient.Option.Recursive);
                System.out.println("Scp finished...");

                if (scpClient != null) {
                    scpClient = null;
                }

                if (session != null && session.isOpen()) {
                    session.close();
                }
                if (client != null && client.isOpen()) {
                    client.stop();
                    client.close();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }
}

第四个

问题

java运行shell命令

Process p = Runtime
                .getRuntime()
                .exec("nohup java -Djava.ext.dirs=nosignlib SshExcute " +
                        "root root 192.168.1.15 /file /file a.txt >ssh.out &");
int rc = p.waitFor();
if (rc != 0) {
	InputStream errorStream = p.getErrorStream();
	BufferedReader reader = new BufferedReader(new InputStreamReader(errorStream));
	String line;
	while ((line = reader.readLine()) != null) {
	    System.out.println(line);
	}
}

遇到了各种问题,有rc是0,但是没执行的,还有其他错误。

解决

java里执行nohup命令,会卡到那无法退出,并且很多问题可以通过sh运行脚本来解决,于是封装个脚本

# ssh_excute.sh
nohup java -Djava.ext.dirs=nosignlib SshExcute $1 $2 $3 $4 $5 $6 >ssh.out &
echo "excute completed..."
exit 0

对应java代码
1、必须用绝对路径,不然会找不到脚本。
2、必须用数组的构造器。字符串的构造器,底层会因特殊字符,将命令转换错误。
3、必须带 -c,要不然底层会把后面命令按空格截断。

String c = "/xxx/ssh_excute.sh root root 192.168.1.15 /file /file a.txt"
String[] command = {"/bin/sh", "-c", c};
ProcessBuilder pb = new ProcessBuilder(command);
p = pb.start();
文章来源:https://blog.csdn.net/qq_36514197/article/details/134994925
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。