當前位置: 首頁>>技術教程>>正文


java – 使用JSch在遠程計算機上執行命令

SSH是訪問遠程計算機,傳輸數據和執行遠程命令的一種簡單而安全的方法。除了基礎的交互模式外,還有許多依賴於ssh Client/Server架構的工具可以實現自動化執行遠程任務。我們可以找到ssh客戶端的許多實現,但是如何從代碼編程訪問ssh提供的功能呢?本文介紹JAVA語言中使用ssh功能的方法。

JSch是一個用Java實現ssh協議的項目。借助它的幫助,您可以構建應用程序連接到遠程或本地SSH服務器並與之交互。這樣,您的應用程序就可以使用本機ssh客戶端在目標計算機上完成絕大部分功能。

在本文中,我們將把JSch導入Java項目中,並編寫少量必要代碼來創建一個可以登錄到遠程計算機的ssh服務器應用程序,在遠程交互式 shell 中執行一些命令、關閉會話、然後顯示輸出。

在本教程中,您將學習:

  • 如何將JSch導入Java項目
  • 如何設置測試環境
  • 如何在自定義類中實現UserInfo接口
  • 如何編寫啟動交互式ssh會話的應用程序

JSch example execution

圖:JSch示例執行。

介紹

我們將開發一個基於JSch的JAVA應用程序,它將通過ssh使用用戶名test和密碼test登錄到localhost。我們假定ssh服務器偵聽默認端口22,並且接受服務器的指紋而不會檢查其有效性。成功登錄後,我們將執行一些命令,然後打印收到的所有輸出。

我們的工具將包括Fedora桌麵(作為客戶端和服務器),最新的NetBeans IDE以及(在撰寫本文時)最新的穩定版JSch。但是請注意,這些隻是選擇的工具。 Java是跨平台或者說平台獨立的,目標服務器可能在地球的另一端,可以是任何正常運行ssh server的操作係統。

設置測試環境

在示例中,我們需要一個名為”test”的用戶,其密碼為”test”。我們還需要一個正在運行的ssh服務器。

添加測試用戶

我們將以root身份執行useradd

# useradd test

並設置新用戶的密碼:

# passwd test

在這裏,我們需要提供兩次以上的密碼。注意:這個簡單賬號密碼適用於臨時的環境,最好是外界無法訪問的測試環境,如果是在極有可能不受控製的訪問時,為了安全起見,請勿使用容易猜到的密碼。

檢查SSH服務器

我們可以使用systemd檢查狀態ssh server

# systemctl status sshd

如果它沒有運行,請啟動它:

# systemctl start sshd

在台式機上,可能需要執行此步驟,因為其中某些設置在默認情況下不會運行ssh服務器。

測試與本機客戶端的連接

如果設置好了用戶且該服務正在運行,則我們應該能夠使用以上信息登錄:

$ ssh test@localhost

我們需要接受主機的指紋並提供密碼。如果登陸成功並進入 shell ,那麽我們的測試環境就準備好了。

獲取並將JSch導入我們的項目

下載文件

為了使用它的功能,我們需要下載JSch項目的字節碼。您可以接在JSch主頁上找到適當的鏈接。我們需要.jar Java文件。

在NetBeans中創建項目

首先,我們在NetBeans中創建一個新的空項目,名為sshRemoteExample。我們可以從File菜單中選擇”New Project”。


 


Creating new project

圖:創建新項目。

我們將選擇”Java”類別和”Java Application”項目。

Choosing category for the project

圖:選擇項目的類別。

我們需要為項目提供一個名稱,在這種情況下為”sshRemoteExample”。

Naming the project

圖:命名項目。

在默認布局上,我們可以在左側找到”Projects”窗口。在新創建的項目下,我們將在”Libraries”上點擊鼠標右鍵,然後選擇“添加JAR /文件夾”。將打開一個文件選擇器窗口,我們需要在其中瀏覽從開發者網站下載的.jar文件。

Adding a JAR as a library

圖:將JAR添加為庫。

選擇之後,如果我們打開”Libraries”節點,則存檔應出現在包含的庫中。

JSch imported successfully

圖:JSch導入成功。

我們需要實現UserInfo接口以便在應用程序中使用它。為此,需要添加一個新java class到項目中:在項目窗口上右鍵點擊sshremoteexample包,選擇”New”,然後選擇“ Java類…”。

Adding new Java class to the package

圖:將新的Java類添加到包中。

我們將以”sshRemoteExampleUserinfo”作為類名。

Naming the new Java class

圖:命名新的Java類。

添加源代碼

對於我們的接口實現,請考慮以下源碼。這是我們盲目接受目標指紋的地方,在現實世界中不要這樣做。

package sshremoteexample;

import com.jcraft.jsch.*;

public class sshRemoteExampleUserInfo implements UserInfo {
    private final String pwd;
    
    public sshRemoteExampleUserInfo (String userName, String password) {
        pwd = password;
    }
    
    @Override
    public String getPassphrase() {
        throw new UnsupportedOperationException("getPassphrase Not supported yet.");
    }
    @Override
    public String getPassword() {
        return pwd;
    }
    @Override
    public boolean promptPassword(String string) {
        /*mod*/
        return true;
    }
    @Override
    public boolean promptPassphrase(String string) {
        throw new UnsupportedOperationException("promptPassphrase Not supported yet.");
    }
    @Override
    public boolean promptYesNo(String string) {
        /*mod*/
        return true;
    }
    @Override
    public void showMessage (String string) {
    }
}

 

Main類是sshRemoteExample,代碼如下

package sshremoteexample;

import com.jcraft.jsch.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

public class SshRemoteExample {

    public static void main(String[] args) {
        String host = "localhost";
        String user = "test";
        String password = "test";
        String command = "hostname\ndf -h\nexit\n";
        try {
            JSch jsch = new JSch();
            Session session = jsch.getSession(user,host, 22);
            session.setUserInfo(new sshRemoteExampleUserInfo(user, password));
            session.connect();
            Channel channel = session.openChannel("shell");
            channel.setInputStream(new ByteArrayInputStream(command.getBytes(StandardCharsets.UTF_8)));
            channel.setOutputStream(System.out);
            InputStream in = channel.getInputStream();
            StringBuilder outBuff = new StringBuilder();
            int exitStatus = -1;
            
            channel.connect();
            
            while (true) {                
                for (int c; ((c = in.read()) >= 0);) {
                    outBuff.append((char) c);
                }
                
                if (channel.isClosed()) {
                    if (in.available() > 0) continue;
                    exitStatus = channel.getExitStatus();
                    break;
                }
            }
            channel.disconnect();
            session.disconnect();
            
        // print the buffer's contents
        System.out.print (outBuff.toString());
        // print exit status
        System.out.print ("Exit status of the execution: " + exitStatus);
        if ( exitStatus == 0 ) {
            System.out.print (" (OK)\n");
        } else {
            System.out.print (" (NOK)\n");
        }
        
        } catch (IOException | JSchException ioEx) {
            System.err.println(ioEx.toString());
        }
    }   
}

請注意,在此示例中,我們硬編碼連接所需的每個詳細信息:目標主機名,用戶名/密碼以及要在遠程會話中執行的命令字符串。在正式編程或生產環境,不建議這樣用硬編碼!

我們可以更改目標和憑據以在遠程主機上執行命令。另請注意,遠程會話將具有登錄用戶的特權。我不建議使用具有高特權的用戶-例如root

運行應用程序

我們可以通過單擊”Run”菜單中的“運行項目(sshRemoteExample)”,直接在IDE中運行應用程序,該菜單將在源代碼下方的輸出窗口中提供輸出。我們還可以從同一菜單中選擇“清理並生成項目(sshRemoteExample)”,在這種情況下,IDE將生成.jar Java歸檔文件,該文件可以在沒有IDE的情況下執行。

提供的輸出將顯示存檔的路徑,類似於以下內容(確切的路徑可能會因您的IDE設置而異):

To run this application from the command line without Ant, try:
java -jar "/var/projects/sshRemoteExample/dist/sshRemoteExample.jar"

可以猜到,我們可以從命令行運行構建的應用程序,如果一切順利,它將提供類似於以下內容的輸出。

$ java -jar "/var/projects/sshShellExample/dist/sshShellExample.jar"
Last login: Mon Jul 29 14:27:08 2019 from 127.0.0.1
hostname
df -h
exit
[test@test1 ~]$ hostname
test1.linuxconfig.org
[test@test1 ~]$ df -h
Filesystem                               Size  Used Avail Use% Mounted on
devtmpfs                                 3,9G     0  3,9G   0% /dev
tmpfs                                    3,9G  127M  3,8G   4% /dev/shm
tmpfs                                    3,9G  1,7M  3,9G   1% /run
tmpfs                                    3,9G     0  3,9G   0% /sys/fs/cgroup
/dev/mapper/fedora_localhost--live-root   49G   15G   32G  32% /
tmpfs                                    3,9G  6,1M  3,9G   1% /tmp
/dev/sdb1                                275G  121G  140G  47% /mnt/hdd_open
/dev/sda2                                976M  198M  711M  22% /boot
/dev/mapper/fedora_localhost--live-home   60G   50G  6,9G  88% /home
/dev/sda1                                200M   18M  182M   9% /boot/efi
tmpfs                                    789M  9,7M  779M   2% /run/user/1000
tmpfs                                    789M     0  789M   0% /run/user/1001
[test@test1 ~]$ exit
logout
Exit status of the execution: 0 (OK)

請注意,您的輸出可能會在主機名,卷名和大小上有所不同(如果沒有其他區別),但總的來說,您應該看到一個完整的df -h輸出,跟在普通ssh會話中獲得的輸出類似。

最後的想法

這個簡單的示例旨在以稍微簡化的方式展示JSch項目的功能。通過訪問測試機和適當的客戶端,以下簡單命令將提供相同的信息:

$ ssh test@localhost "hostname; df -h"

並且不會創建交互式會話。如果在命令模式下打開通道,JSch將提供相同的功能:

Channel channel = session.openChannel("command");

這樣,您無需使用exitShell命令。

該項目的真正力量在於能夠通過本機Shell命令連接到遠程機器並與之交互,處理輸出並以編程方式確定下一步操作的能力。想象一下一個多線程的應用程序,該應用程序通過這種方式可以管理數百個服務器。

參考資料

本文由《純淨天空》出品。文章地址: https://vimsky.com/zh-tw/article/4548.html,未經允許,請勿轉載。