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年第一篇博客,新開始,新徵程,小步慢跑,加油努力,做任何事都不會容易。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章