2022年9月19日 星期一

用 Java 進行 SSH 連線 - 使用 JSch

 在這篇文中要展示如何用 Java 以 JSch 這個 library 來進行 SSH 連線,

首先是用 Maven 引入 library :

<!-- https://mvnrepository.com/artifact/com.jcraft/jsch -->
<dependency>
	<groupId>com.jcraft</groupId>
	<artifactId>jsch</artifactId>
	<version>0.1.55</version>
</dependency>
接著先直接上程式碼:
package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;

public class SftpTest {

	public static void main(String[] args) throws Exception {
		
		String privateKeyPath = "D:\\...\xx.pem"; //your private key file path
		String userName = "xxName"; //your username to access target server
		String host = "xxx.xxx.xxx.xxx"; //hostname of target server
		int port = 22; //target server port
		
		Session session = null;
		
		try (InputStream inputStreamOfPrivateKey = new FileInputStream(new File(privateKeyPath))){			
	        JSch jsch = new JSch();	        
	         
	        jsch.addIdentity(privateKeyPath, inputStreamOfPrivateKey.readAllBytes(), null, null);
	        session = jsch.getSession(userName, host, port);
            //session.setPassword("xxxPassword");
	        session.setConfig("StrictHostKeyChecking", "no");
	        session.setTimeout(60000);
	        session.connect();
			
	        ChannelSftp channelSftp = (ChannelSftp) session.openChannel("sftp");
	        channelSftp.connect();
	        
	        //check if file exists
	        System.out.println(isFileExist(channelSftp, "/xxx/xxx.png"));
	        
	        //check file attributes
	        SftpATTRS fileAttrs = channelSftp.lstat("/xxx/xxx.png");
	        System.out.println(fileAttrs.getSize());
	        
	        //create directory
	        mkdir(channelSftp, "/xxx/xxx");
	        
	        //upload file
	        upload(channelSftp, "D:\\xxx\\xxx.jpg", "/xxx/xxx");

	        channelSftp.exit();
		}catch(Exception e) {
			e.printStackTrace();
		} finally {
			if (session != null && session.isConnected()) {
				session.disconnect();
			}			
		}
		System.out.println("Done");
	}
	
	public static void upload(ChannelSftp channelSftp, String srcFilePath, String targetDirectoryPath) {
    	targetDirectoryPath = targetDirectoryPath.replace("\\", "/");
    	
    	File srcFile = new File(srcFilePath);
    	try (FileInputStream fileInputStream = new FileInputStream(srcFile)){
    		if (isFileExist(channelSftp, targetDirectoryPath)) {
        		channelSftp.cd(targetDirectoryPath);
        	}else {
        		channelSftp.cd("/"); //go to root path
        		
        		String[] targetDirectoryNameList = targetDirectoryPath.split("/");
        		for(String targetDirectoryName : targetDirectoryNameList) {
        			if ("".equals(targetDirectoryName)) {
        				continue;
        			}
        			if (!isFileExist(channelSftp, targetDirectoryName)) {
        				channelSftp.mkdir(targetDirectoryName);
        			}
        			channelSftp.cd(targetDirectoryName);
        		}
        	}
    		channelSftp.put(fileInputStream, srcFile.getName());
    	} catch (IOException | SftpException e) {
			e.printStackTrace();
		}
    }
	
	static void mkdir(ChannelSftp channelSftp, String directoryPath) {
		directoryPath = directoryPath.replace("\\", "/");

    	try {
    		channelSftp.cd("/"); //go to root path
    		
    		String[] directoryNameList = directoryPath.split("/");
    		for(String directoryName : directoryNameList) {
    			if ("".equals(directoryName)) {
    				continue;
    			}
    			if (!isFileExist(channelSftp, directoryName)) {
    				channelSftp.mkdir(directoryName);
    			}
    			channelSftp.cd(directoryName);
    		}
    	} catch (SftpException e) {
			e.printStackTrace();
		}
	}
	
	static boolean isFileExist(ChannelSftp channelSftp, String filePath) {
		boolean isFileExist = false;
		
		try {
			channelSftp.lstat(filePath);
			isFileExist = true;
		} catch (SftpException e) {
			if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
				isFileExist = false;
			}
		}
		
		return isFileExist;
	}

}

說明:
上面程式碼的情境是想要 SSH 連線至一台 Linux Server,
且已經設定好 ssh public key 並放在 server 的 .ssh 資料夾裡,
而我們本地的機器上也已經有一個對應的 ssh private key,
所以我們只需使用 private key 來進行連線而不用使用密碼。

session.setConfig("StrictHostKeyChecking", "no");
這行程式碼設定了 StrictHostKeyChecking 為 no,
是因為在 SSH 連線時有時會需要終端機互動,
例如在使用命令式模式指令進行 SSH 連線操作時,
server 可能會跳出一些訊息要你輸入 "yes" 等動作才能進行下一步,
設定 StrictHostKeyChecking 為 no 就可以避免這種互動式操作,
可參考:

在程式碼中可以看到,取得了 ChannelSftp 實例後,
就可以使用它來進行對 server 的操作,例如: cd, pwd, ls, mkdir, ... 等,
就像我們平台用命令列模式指令一樣,當使用了 cd 指令後,
會把我們帶到特定的 server 上的檔案路徑位置,
並且可以以目前所在的位置用相對路徑進行 ls, mkdir, ... 等操作,
當然也可使用絕對路徑進行操作。

在上述程式碼中,
我包裝並實作了 isFileExist(), upload(), mkdir() 等 methild 
來取代直接使用 ChannelSftp 自身的 API,
在實作的 method 中限定了檔案路徑參數必須為絕對路徑以避免不必要的
路徑處理麻煩,
isFileExist() 使用了 ChannelSftp.lstat() 和 ChannelSftp.SSH_FX_NO_SUCH_FILE 的補獲來實作。
upload() 和 mkdir() 使用 isFileExist() 來判斷路徑上資料夾的存在與否,
如果不存在的話則幫忙建立相應的資料夾。

沒有留言 :

張貼留言