java sshd实现连接ssh操作

说明

  • 本博客每周五更新一次。
  • 日常使用ssh连接工具是jsch实现,但该库从2018年开始停止更新,项目开发中使用免密登录功能时,因为ssh加密算法版本过低失败,最后不得不使用账号密码连接。那次后,萌生了寻找替代方案的想法,并找到了sshd库。

分享

记录

导包

maven导包如下:

<dependency>
    <groupId>org.apache.sshd</groupId>
    <artifactId>sshd-scp</artifactId>
    <version>2.8.0</version>
</dependency>

代码

实例代码实现了远程命令执行、scp文件上传和下载,基于sftp的未实现。


import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.channel.ChannelExec;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.scp.client.DefaultScpClientCreator;
import org.apache.sshd.scp.client.ScpClient;
import org.apache.sshd.scp.client.ScpClientCreator;
import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.impl.DefaultSftpClientFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.tool.cn.model.SshModel;


/**
 * 
 * @author wangzonghui
 * @date 2022年2月7日 上午9:55:58 
 * @Description ssh工具类测试
 */
public final class SshSshdUtil {
	
	private  final static Logger log = LoggerFactory.getLogger(SshSshdUtil.class);
	
	private String host;
	private String user;
	private String password;
	private int port;
	
	 private ClientSession session;
	 private  SshClient client;
	
	/**
	 * 创建一个连接
	 * 
	 * @param host     地址
	 * @param user     用户名
	 * @param port     ssh端口
	 * @param password 密码
	 */
	public SshSshdUtil(String host,String user,int port,String password) {
		this.host = host;
		this.user = user;
		this.port=port;
		this.password=password;
	}

	/**
	 * 登录
	 * @return
	 * @throws Exception
	 */
	public boolean initialSession() {
		if (session == null) {
			
			try {
				// 创建 SSH客户端
				client = SshClient.setUpDefaultClient();
				// 启动 SSH客户端
				client.start();
				// 通过主机IP、端口和用户名,连接主机,获取Session
				session = client.connect(user, host, port).verify().getSession();
				// 给Session添加密码
				session.addPasswordIdentity(password);
				// 校验用户名和密码的有效性
				return session.auth().verify().isSuccess();
				
			} catch (Exception e) {
				log.info("Login Host:"+host+" Error",e);
				return false;
			}
		}
		
		return true;
	}

	/**
	 * 关闭连接
	 * @throws Exception
	 */
	public void close() throws Exception  {
		//关闭session
		if (session != null && session.isOpen()) {
            session.close();
        }
		
		// 关闭 SSH客户端
        if (client != null && client.isOpen()) {
            client.stop();
            client.close();
        }
	}
	
	/**
	 * 下载文件 基于sftp
	 * 
	 * @param localFile  本地文件名,若为空或是*,表示目前下全部文件
	 * @param remotePath 远程路径,若为空,表示当前路径,若服务器上无此目录,则会自动创建
	 * @throws Exception
	 */
	public boolean sftpGetFile(String localFile, String remoteFile) {
		SftpClient sftp=null;
		InputStream is=null;
		try {
			if(this.initialSession()) {
				DefaultSftpClientFactory sftpFactory=new DefaultSftpClientFactory();
				sftp=sftpFactory.createSftpClient(session);
				is=sftp.read(remoteFile);
		        Path dst=Paths.get(localFile);
		        Files.deleteIfExists(dst);
		        Files.copy(is, dst);
		        
			}
		} catch (Exception e) {
			log.error(host+"Local File "+localFile+" Sftp Get File:"+remoteFile+" Error",e);
			return false;
		}finally {
			try {
				if(is!=null) {
					is.close();
				}
				if(sftp!=null) {
					sftp.close();
				}
				
			} catch (Exception e) {
				log.error("Close Error",e);
			}
			
		}
		return true;
	}
	
	/**
	 * 下载文件 基于sftp
	 * 
	 * @param localFile  本地文件名,若为空或是*,表示目前下全部文件
	 * @param remotePath 远程路径,若为空,表示当前路径,若服务器上无此目录,则会自动创建
	 * @param outTime 超时时间 单位毫秒
	 * @throws Exception
	 */
	public boolean sftpGetFile(String localFile, String remoteFile,int timeOut) {
		ProcessWithTimeout process=new ProcessWithTimeout(localFile,remoteFile,1);
		int exitCode=process.waitForProcess(timeOut);
		
		if(exitCode==-1) {
			log.error("{} put Local File {} To Sftp Path:{} Time Out",host,localFile,remoteFile);
		}
		return exitCode==0?true:false;
	}

	/**
	 * 上传文件 基于sftp
	 * 
	 * @param localFile  本地文件名,若为空或是*,表示目前下全部文件
	 * @param remotePath 远程路径,若为空,表示当前路径,若服务器上无此目录,则会自动创建
	 * @param outTime 超时时间 单位毫秒
	 * @throws Exception
	 */
	public boolean sftpPutFile(String localFile, String remoteFile,int timeOut) {
		ProcessWithTimeout process=new ProcessWithTimeout(localFile,remoteFile,0);
		int exitCode=process.waitForProcess(timeOut);
		
		if(exitCode==-1) {
			log.error("{} put Local File {} To Sftp Path:{} Time Out",host,localFile,remoteFile);
		}
		return exitCode==0?true:false;
	}
	
	/**
	 * 
	 * @author wangzonghui
	 * @date 2022年4月13日 下午2:15:17 
	 * @Description 任务执行线程,判断操作超时使用
	 */
	class ProcessWithTimeout extends Thread{
		
		private String localFile;
		private String remoteFile;
		private int type;
		private int exitCode =-1;
		
		/**
		 * 
		 * @param localFile 本地文件
		 * @param remoteFile  sftp服务器文件
		 * @param type 0 上传  1 下载
		 */
		public ProcessWithTimeout(String localFile, String remoteFile,int type) {
			this.localFile=localFile;
			this.remoteFile=remoteFile;
			this.type=type;
		}
		
		public int waitForProcess(int outtime){
			  this.start();
			  
			  try{
			     this.join(outtime);
			  }catch (InterruptedException e){
			   log.error("Wait Is Error",e);
			  }
			  
			  return exitCode;
		 }
		
		@Override
		public void run() {
			super.run();
			boolean state;
			if(type==0) {
				state=sftpPutFile(localFile, remoteFile);
			}else {
				state=sftpGetFile(localFile, remoteFile);
			}
			
			exitCode= state==true?0:1;
		}
	}
	
	/**
	 * 上传文件 基于sftp
	 * 
	 * @param localFile  本地文件名,若为空或是*,表示目前下全部文件
	 * @param remotePath 远程路径,若为空,表示当前路径,若服务器上无此目录,则会自动创建
	 * @throws Exception
	 */
	public boolean sftpPutFile(String localFile, String remoteFile) {
		SftpClient sftp=null;
		OutputStream os=null;
		try {
			if(this.initialSession()) {
				DefaultSftpClientFactory sftpFactory=new DefaultSftpClientFactory();
				sftp=sftpFactory.createSftpClient(session);

				os=sftp.write(remoteFile,1024);
		        Files.copy(Paths.get(localFile), os);
			}

		} catch (Exception e) {
			log.error(host+"Local File "+localFile+" Sftp Upload File:"+remoteFile+" Error",e);
			return false;
		}finally {
			try {
				if(os!=null) {
					os.close();
				}
				if(sftp!=null) {
					sftp.close();
				}
				
			} catch (Exception e) {
				log.error("Close Error",e);
			}
		}
		return true;
	}

	/**
	 * 上传文件 基于scp
	 * 
	 * @param localFile  本地文件名,若为空或是*,表示目前下全部文件
	 * @param remotePath 远程路径,若为空,表示当前路径,若服务器上无此目录,则会自动创建
	 * @throws Exception
	 */
	public boolean scpPutFile( String localFile, String remoteFile) {
		ScpClient scpClient=null;
		try {
			if(this.initialSession()) {
				ScpClientCreator creator = new DefaultScpClientCreator();

	            // 创建 SCP 客户端
	            scpClient = creator.createScpClient(session);

	            // ScpClient.Option.Recursive:递归copy,可以将子文件夹和子文件遍历copy
	            scpClient.upload(localFile, remoteFile, ScpClient.Option.Recursive);
	            
			}else {
				log.error("Host:{} User:{} Upload Local File:{} Error",host,user,localFile);
				return false;
			}
			
		} catch (Exception e) {
			log.error(e.toString(), e);
			return false;
		}finally {
			// 释放 SCP客户端
            if (scpClient != null) {
                scpClient = null;
            }
		}

		return true;
	}

	/**
	 * 下载文件 基于scp
	 * 
	 * @param localPath  本地路径,若为空,表示当前路径
	 * @param localFile  本地文件名,若为空或是*,表示目前下全部文件
	 * @param remotePath 远程路径,若为空,表示当前路径,若服务器上无此目录,则会自动创建
	 * @throws Exception
	 */
	public boolean scpGetFile( String localFile, String remoteFile) {
		ScpClient scpClient=null;
		try {
			if(this.initialSession()) {
				ScpClientCreator creator = new DefaultScpClientCreator();
	            // 创建 SCP 客户端
	            scpClient = creator.createScpClient(session);

	            scpClient.download(remoteFile, localFile);  //下载文件
	            
			}else {
				log.error("Host:{} User:{} Get File:{} Error",host,user,remoteFile);
				return false;
			}
			
		} catch (Exception e) {
			log.error(e.toString(), e);
			return false;
		}finally {
			 // 释放 SCP客户端
            if (scpClient != null) {
                scpClient = null;
            }
            
		}

		return true;
	}
	
	/**
	 * 执行远程命令 
	 * @param command 执行的命令
	 * @return 0成功 1异常
	 * @throws Exception
	 */
	public int runCommand(String command)  {
		ChannelExec channel=null;
		try {
			if(this.initialSession()) {
				channel=session.createExecChannel(command);
				int time = 0;
				boolean run = false;
				
				channel.open();
				ByteArrayOutputStream err = new ByteArrayOutputStream();
				channel.setErr(err);
				
		        
				while (true) {
					if (channel.isClosed() || run) {
						break;
					}
					try {
						Thread.sleep(100);
					} catch (Exception e) {
						
					}
					if (time > 1800) {
						break;
					}
					time++;
				}

				int status=channel.getExitStatus();
				
				if(status>0) {
					log.info("{}  host:{} user:{} Run Is code:{} Message:{}",command,host,user,status,err.toString());
				}
				
				return status;
			}else {
				log.error("Host:{} User:{} Login Error",host,user);
				return 1;
			}
			
		} catch (Exception e) {
			log.error("Host "+host+" Run Command Error:"+command+" " +e.toString(),e);
			return 1;
		}finally {
			if(channel!=null) {
				try {
					channel.close();
				} catch (Exception e) {
					log.error("Close Connection Error");
				}
			}
			
		}
	}

	/**
	 * 执行远程命令 
	 * @param command 执行的命令
	 * @return 0成功 其他 异常
	 * @throws Exception
	 */
	public SshModel run(String command)  {
		SshModel sshModel=new SshModel();
		ChannelExec channel=null;
		try {
			if(this.initialSession()) {
				channel=session.createExecChannel(command);
				int time = 0;
				boolean run = false;

				channel.open();
				ByteArrayOutputStream info = new ByteArrayOutputStream();
				channel.setOut(info);
				ByteArrayOutputStream err = new ByteArrayOutputStream();
				channel.setErr(err);
		        
				while (true) {
					if (channel.isClosed() || run) {
						break;
					}
					try {
						Thread.sleep(100);
					} catch (Exception ee) {
					}
					if (time > 180) {

						break;
					}
					time++;
				}
		        
		        int status=channel.getExitStatus();
				
				sshModel.setCode(status);
				sshModel.setInfo(info.toString());
				sshModel.setError(err.toString());
			}else {
				log.error("Host:{} User:{} Login Error",host,user);
				sshModel.setCode(1);
				sshModel.setError("Loging Error");
			}
			
		} catch (Exception e) {
			log.error("Host "+host+"Run Command Error:"+command+" " +e.toString(),e);
			sshModel.setCode(1);
			
		}finally {
			if(channel!=null) {
				try {
					channel.close();
				} catch (IOException e) {
					log.error("Close Connection Error");
				}
			}
		}
		return sshModel;
	}
}
  • 引用数据模型类SshModel
package ssh.model.cn;

/**
 * 
 * @author wangzonghui
 * @date 2020年8月14日 下午3:09:24 
 * @Description:ssh模型类
 */
public class SshModel {
	
	/**
	 * 状态码
	 */
	private int code;
	
	/**
	 * 信息
	 */
	private String info;
	
	/**
	 * 错误信息
	 */
	private String error;

	public int getCode() {
		return code;
	}

	public void setCode(int code) {
		this.code = code;
	}

	public String getInfo() {
		return info;
	}

	public void setInfo(String info) {
		this.info = info;
	}

	public String getError() {
		return error;
	}

	public void setError(String error) {
		this.error = error;
	}

	@Override
	public String toString() {
		return "SshModel [code=" + code + ", info=" + info + ", error=" + error + "]";
	}
	
}

总结

  • 学习技术是个漫长有趣的过程,同样功能的更好方式实现,更高效执行,功能的快速开发,都是自我的价值体现。
  • 本文为2022年第一篇博客,新开始,新征程,小步慢跑,加油努力,做任何事都不会容易。
  • 7
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值