以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/03/08/190208より取得しました。


Apache MINA SSHDでSSH/SFTPクライアントを使う(+JavaのSSHクライアントライブラリー事情)

これは、なにをしたくて書いたもの?

最近使うことが減ってきた気がするSSHクライアントなどですが、Javaで扱おうとするとJSchが有名かと思います。

このあたりの事情を最近見直すとちょっと変わっていた感じがするのと、今回はApache MINA SSHDSSHやSFTPの
クライアントを扱ってみたいと思います。

JavaSSHクライアントライブラリー

Javaで使えるSSHクライアントライブラリーは、以前にまとめたことがあります。といっても13年前ですが…。

Javaで使えるSSHライブラリ - CLOVER🍀

今調べ直すと、こんな感じになっていました。

JSch

Javaでよく使われるSSHクライアントライブラリーといえばJSchですが、現在はメンテナンスが止まっています。

JSch download | SourceForge.net

GitHub - is/jsch: Mirror of JSch from JCraft.

最後のリリースは、2018年でしょうか。

なお、フォークが存在します。現在はJSchといえばこちらを使う方もいるようです。

GitHub - mwiede/jsch: fork of the popular jsch library

Apache MINA SSHD

Apache MINA SSHDは、SSHクライアント・サーバーの両方を扱えるライブラリーです。

SSHD Overview — Apache MINA

以前(これも10年くらい前ですが…)にSSHサーバーとして試したことがあります。

Apache MINAで簡単SSHD - CLOVER🍀

現在でもメンテナンスがアクティブに続けられています。

GitHub - apache/mina-sshd: Apache MINA sshd is a comprehensive Java library for client- and server-side SSH.

SSHJ

JSchと同じく、かなり前からあるSSHライブラリーです。

GitHub - hierynomus/sshj: ssh, scp and sftp for java

現在でもメンテナンスが続けられています。また操作もわかりやすそうです。

Ganymed SSH-2 for Java

こちらもJSchと同じく、かなり前からあるSSHライブラリーです。

GitHub - SoftwareAG/ganymed-ssh-2: Ganymed SSH-2 for Java is a library which implements the SSH-2 protocol in pure Java.

こちらもメンテナンスはそれほどアクティブではなさそうです。最終リリースは2020年です。

Apache MINA SSHDを使う

JSchの更新が止まっていることを知らなかったので、ちょっと驚きました。

では今はどれが使われているのかなと思って調べてみたところ、Apache MINA SSHDが多そうです。

たとえばSpring IntegrationのSFTP Adaptersは、JSchからApache MINA SSHDに移行したようです。

Starting with version 6.0, an outdated JCraft JSch client has been replaced with modern Apache MINA SSHD framework. This caused a lot of breaking changes in the framework components.

SFTP Adapters :: Spring Integration

もっとも、利用者にはその変更があまりわからないようになっているみたいですが。

However, in most cases, such a migration is hidden behind Spring Integration API. The most drastic changed has happened with a DefaultSftpSessionFactory which is based now on the org.apache.sshd.client.SshClient and exposes some if its configuration properties.

ちなみにちょっと驚いたこととしては、Apache MINA SSHD側にSpring Integration互換のモジュールがあったりします。

https://github.com/apache/mina-sshd/tree/sshd-2.15.0/sshd-spring-sftp

Spring Integration自身はこのモジュールは使っていません。

というわけで、今回はApache MINA SSHDを見ていこうと思います。

ドキュメントはGitHubリポジトリー内にあります。

https://github.com/apache/mina-sshd/tree/sshd-2.15.0/docs

サポートしている機能はこちら。

https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/standards.md

基本的にREADME.mdからドキュメントを読み進めていけばよいでしょう。

https://github.com/apache/mina-sshd/tree/sshd-2.15.0

とはいえ、ドキュメントがちょっと読み解きづらいので、今回はメモを兼ねてSSHとSFTPを使ってみたいと思います。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.6 2025-01-21
OpenJDK Runtime Environment (build 21.0.6+7-Ubuntu-124.04.1)
OpenJDK 64-Bit Server VM (build 21.0.6+7-Ubuntu-124.04.1, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.6, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-55-generic", arch: "amd64", family: "unix"

また接続先のSSHサーバーは、Ubuntu Linux 24.04 LTSとします。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.2 LTS
Release:        24.04
Codename:       noble


$ uname -srvmpio
Linux 6.8.0-55-generic #57-Ubuntu SMP PREEMPT_DYNAMIC Wed Feb 12 23:42:21 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux


$ sshd -V
OpenSSH_9.6p1 Ubuntu-3ubuntu13.8, OpenSSL 3.0.13 30 Jan 2024

IPアドレスは192.168.0.6、ポートは2022とします。

準備

SSHサーバー側の準備をします。

ユーザーを作成。

$ sudo adduser ssh-testuser

SSHサーバーには、パスワードと公開鍵認証の両方でログインできるようにしておきます。ふつうはやったとしても
後者だけだと思いますが、確認という意味で…。

$ sudo su - ssh-testuser

このユーザーのホームディレクトリーにファイルやディレクトリーを作成しておきます。

$ echo 'Hello from Server' > hello-server.txt
$ mkdir remote-dir
$ echo 'Hello from Server in remote-dir' > remote-dir/hello-server-in-dir.txt
$ mkdir remote-dest-dir

ログイン用の公開鍵も作成しましょう。これはクライアント側で作成します。アルゴリズムはed25519です。

$ mkdir ssh-keys
$ ssh-keygen -f ssh-keys/testkey
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in ssh-keys/testkey
Your public key has been saved in ssh-keys/testkey.pub
The key fingerprint is:
SHA256:2I5UU/izS7EWXpXW+fTWf8yV8gPIswp67eJE+1su08E test@client
The key's randomart image is:
+--[ED25519 256]--+
|         ..    o.|
|        ..    +.o|
|        o.. .o .=|
|       + .=+.o .*|
|      o.S..Bo ++o|
|     ..+. *E   o=|
|      oo+ooo.   o|
|     ..o.=+.     |
|      o.o++.     |
+----[SHA256]-----+

SSHキーができました。パスフレーズは設定ありにしました。

$ ll ssh-keys
合計 16
drwxrwxr-x 2 xxxxx xxxxx 4096  3月  8 16:40 ./
drwxrwxr-x 5 xxxxx xxxxx 4096  3月  8 16:39 ../
-rw------- 1 xxxxx xxxxx  464  3月  8 16:40 testkey
-rw-r--r-- 1 xxxxx xxxxx   93  3月  8 16:40 testkey.pub

公開鍵をサーバー側に置きます。

$ mkdir $HOME/.ssh
$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIeWtk8ud7ehRVLWEzaZIjcFHiMavsTJzf7HB6sfkIii test@client' > $HOME/.ssh/authorized_keys
$ chmod 700 $HOME/.ssh
$ chmod 600 $HOME/.ssh/authorized_keys

あとはApache MINA SSHDを使う準備をしておきましょう。

Maven依存関係など。

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.apache.sshd</groupId>
            <artifactId>sshd-sftp</artifactId>
            <version>2.15.0</version>
        </dependency>
        <dependency>
            <groupId>net.i2p.crypto</groupId>
            <artifactId>eddsa</artifactId>
            <version>0.3.0</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.12.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.27.3</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.36</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

SFTPまで使うのでsshd-sftpを依存関係に含めていますが、SSHクライアントのみ使う場合はsshd-coreがあればOKです。

        <dependency>
            <groupId>org.apache.sshd</groupId>
            <artifactId>sshd-core</artifactId>
            <version>2.15.0</version>
        </dependency>

Apache MINA SSHDはデフォルトでOpenSSHフォーマットの鍵を読むことができるのですが、アルゴリズムにed25519を
使っている場合には以下の依存関係が必要です。

        <dependency>
            <groupId>net.i2p.crypto</groupId>
            <artifactId>eddsa</artifactId>
            <version>0.3.0</version>
        </dependency>

また、なぜかBouncy Castleを追加しても読めるようになります。

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpg-jdk18on</artifactId>
            <version>1.80</version>
        </dependency>
        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk18on</artifactId>
            <version>1.80</version>
        </dependency>

今回はeddsaを使いますが。

確認はテストコードで行うことにします。

これで準備完了です。

Apache MINA SSHDで、SSH/SFTPクライアントを使う

では、Apache MINA SSHDSSH/SFTPクライアントを使っていきます。

以下の組み合わせでそれぞれ書いていきます。

  • パスワード認証でSSH接続して、SSHでコマンド実行
  • 公開鍵認証でSSH接続して、SSHでコマンド実行
  • パスワード認証でSSH接続して、SFTP操作を実行
  • 公開鍵認証でSSH接続して、SFTP操作を実行

クライアントサイドの場合、最初に見ておくドキュメントはこちらでしょうか。

https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/security-providers.md

用意したテストコードの雛形はこちら。

src/test/java/org/littlewings/mina/sshd/SshClientTest.java

package org.littlewings.mina.sshd;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Collection;
import java.util.EnumSet;
import org.apache.sshd.client.SshClient;
import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
import org.apache.sshd.client.channel.ClientChannel;
import org.apache.sshd.client.channel.ClientChannelEvent;
import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.config.keys.FilePasswordProvider;
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.util.security.SecurityUtils;
import org.apache.sshd.sftp.client.SftpClient;
import org.apache.sshd.sftp.client.SftpClientFactory;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class SshClientTest {
    private static final String SSH_HOST = "192.168.0.6";
    private static final int SSH_PORT = 2022;
    private static final String SSH_USERNAME = "ssh-testuser";
    private static final String SSH_PASSWORD = "password";
    private static final String SSH_PRIVATE_KEY = "ssh-keys/testkey";
    private static final String SSH_KEY_PASSPHRASE = "keypassword";

    // ここに、テストを書く!
}

ここからは、それぞれのバリエーションを書いていきます。

パスワード認証でSSH接続して、SSHでコマンド実行

最初は1番単純な(?)、パスワード認証でSSH接続してからコマンド実行します。

参考にするドキュメントはこちらです。

https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/client-setup.md

作成したテストコードはこちら。

    @Test
    void sshClientAuthenticatePassword() throws IOException {
        try (SshClient sshClient = SshClient.setUpDefaultClient()) {
            sshClient.start();

            try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) {
                // パスワード
                clientSession.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(SSH_PASSWORD));
                // ログイン
                clientSession.auth().verify();

                try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l")) {
                    // 標準出力/エラー出力の設定
                    ByteArrayOutputStream outBaos = new ByteArrayOutputStream();
                    ByteArrayOutputStream errBaos = new ByteArrayOutputStream();
                    clientChannel.setOut(outBaos);
                    clientChannel.setErr(errBaos);

                    // コマンド実行
                    clientChannel.open().verify();
                    // コマンド終了待ち
                    clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);

                    System.out.println(outBaos.toString(StandardCharsets.UTF_8));

                    assertThat(outBaos.toString(StandardCharsets.UTF_8))
                            .contains("hello-server.txt", "remote-dir", "remote-dest-dir");
                    assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty();
                }

                try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l remote-dir")) {
                    ByteArrayOutputStream outBaos = new ByteArrayOutputStream();
                    ByteArrayOutputStream errBaos = new ByteArrayOutputStream();
                    clientChannel.setOut(outBaos);
                    clientChannel.setErr(errBaos);

                    clientChannel.open().verify();
                    clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);

                    System.out.println(outBaos.toString(StandardCharsets.UTF_8));

                    assertThat(outBaos.toString(StandardCharsets.UTF_8))
                            .contains("hello-server-in-dir.txt");
                    assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty();
                }
            }
        }
    }

順を追って説明していきます。

最初にSshClientインスタンスを作成。

        try (SshClient sshClient = SshClient.setUpDefaultClient()) {
            sshClient.start();

接続先やユーザー名を指定して、ClientSessionを作成します。

            try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) {
                // パスワード
                clientSession.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(SSH_PASSWORD));
                // ログイン
                clientSession.auth().verify();

パスワードの設定タイミングはClientSessionの取得後で、最後にClientSession#authからverifyを行うことでログインできます。

あとはコマンド実行ですが、ClientChannel経由で行います。

                try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l")) {

標準出力、標準エラー出力を設定できるので、今回はByteArrayOutputStreamに溜め込んで結果を確認できるようにしました。

                    // 標準出力/エラー出力の設定
                    ByteArrayOutputStream outBaos = new ByteArrayOutputStream();
                    ByteArrayOutputStream errBaos = new ByteArrayOutputStream();
                    clientChannel.setOut(outBaos);
                    clientChannel.setErr(errBaos);

                    // コマンド実行
                    clientChannel.open().verify();
                    // コマンド終了待ち
                    clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);

                    System.out.println(outBaos.toString(StandardCharsets.UTF_8));

                    assertThat(outBaos.toString(StandardCharsets.UTF_8))
                            .contains("hello-server.txt", "remote-dir", "remote-dest-dir");
                    assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty();

コマンド実行の後に

                    // コマンド実行
                    clientChannel.open().verify();

実行終了を待つところがポイントで、これを入れなかったら結果の取得が不安定になってとてもハマりました…。

                    // コマンド終了待ち
                    clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);

こんなところですね。

ちなみに、標準出力に書き出された結果をSystem.out.printlnしている箇所がありますが、その結果はこちらです。

合計 12
-rw-rw-r-- 1 ssh-testuser ssh-testuser   18  3月  8 16:34 hello-server.txt
drwxrwxr-x 2 ssh-testuser ssh-testuser 4096  3月  8 18:19 remote-dest-dir
drwxrwxr-x 2 ssh-testuser ssh-testuser 4096  3月  8 16:34 remote-dir

合計 4
-rw-rw-r-- 1 ssh-testuser ssh-testuser 32  3月  8 18:18 hello-server-in-dir.txt
公開鍵認証でSSH接続して、SSHでコマンド実行

次は公開鍵認証を使って接続します。

結果はこちら。

    @Test
    void sshClientAuthenticateKey() throws IOException, GeneralSecurityException {
        try (SshClient sshClient = SshClient.setUpDefaultClient()) {
            // SSH鍵の設定
            Collection<KeyPair> keys =
                    SecurityUtils
                            .getKeyPairResourceParser()
                            .loadKeyPairs(null, null, FilePasswordProvider.of(SSH_KEY_PASSPHRASE), Files.newBufferedReader(Path.of(SSH_PRIVATE_KEY), StandardCharsets.UTF_8));
            sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys));

            sshClient.start();

            try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) {
                // ログイン
                clientSession.auth().verify();

                try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l")) {
                    // 標準出力/エラー出力の設定
                    ByteArrayOutputStream outBaos = new ByteArrayOutputStream();
                    ByteArrayOutputStream errBaos = new ByteArrayOutputStream();
                    clientChannel.setOut(outBaos);
                    clientChannel.setErr(errBaos);

                    // コマンド実行
                    clientChannel.open().verify();
                    // コマンド終了待ち
                    clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);

                    System.out.println(outBaos.toString(StandardCharsets.UTF_8));

                    assertThat(outBaos.toString(StandardCharsets.UTF_8))
                            .contains("hello-server.txt", "remote-dir", "remote-dest-dir");
                    assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty();
                }

                try (ClientChannel clientChannel = clientSession.createExecChannel("ls -l remote-dir")) {
                    ByteArrayOutputStream outBaos = new ByteArrayOutputStream();
                    ByteArrayOutputStream errBaos = new ByteArrayOutputStream();
                    clientChannel.setOut(outBaos);
                    clientChannel.setErr(errBaos);

                    clientChannel.open().verify();
                    clientChannel.waitFor(EnumSet.of(ClientChannelEvent.CLOSED), 0L);

                    System.out.println(outBaos.toString(StandardCharsets.UTF_8));

                    assertThat(outBaos.toString(StandardCharsets.UTF_8))
                            .contains("hello-server-in-dir.txt");
                    assertThat(errBaos.toString(StandardCharsets.UTF_8)).isEmpty();
                }
            }
        }
    }

パスワードの設定はなくなりました。

            try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) {
                // ログイン
                clientSession.auth().verify();

代わりに、SSH秘密鍵を設定しています。

            // SSH鍵の設定
            Collection<KeyPair> keys =
                    SecurityUtils
                            .getKeyPairResourceParser()
                            .loadKeyPairs(null, null, FilePasswordProvider.of(SSH_KEY_PASSPHRASE), Files.newBufferedReader(Path.of(SSH_PRIVATE_KEY), StandardCharsets.UTF_8));
            sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys));

            sshClient.start();

今回はSshClient全体に設定していますが、ClientSession単位とすることもできるようです。

Set up an SSH client in 5 minutes / ClientIdentityLoader/KeyPairProvider

あとの部分は同じですね。

ちなみに以下の依存関係が入っていなかった場合は

        <dependency>
            <groupId>net.i2p.crypto</groupId>
            <artifactId>eddsa</artifactId>
            <version>0.3.0</version>
        </dependency>

こんな例外がスローされることになります(アルゴリズムをed25519にしている場合)。

java.security.NoSuchAlgorithmException: Unsupported key type (ssh-ed25519) in null
        at org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser.readPublicKey(OpenSSHKeyPairResourceParser.java:221)
        at org.apache.sshd.common.config.keys.loader.openssh.OpenSSHKeyPairResourceParser.extractKeyPairs(OpenSSHKeyPairResourceParser.java:133)
        at org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser.extractKeyPairs(AbstractKeyPairResourceParser.java:198)
        at org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser.extractKeyPairs(AbstractKeyPairResourceParser.java:167)
        at org.apache.sshd.common.config.keys.loader.AbstractKeyPairResourceParser.loadKeyPairs(AbstractKeyPairResourceParser.java:117)
        at org.apache.sshd.common.config.keys.loader.KeyPairResourceParser$2.loadKeyPairs(KeyPairResourceParser.java:166)
        at org.apache.sshd.common.config.keys.loader.KeyPairResourceLoader.loadKeyPairs(KeyPairResourceLoader.java:157)
パスワード認証でSSH接続して、SFTP操作を実行

次はSFTPです。参照するドキュメントはこちら。

https://github.com/apache/mina-sshd/blob/sshd-2.15.0/docs/sftp.md

まずはパスワード認証です。実は、途中までは先ほどまでと同じです。

    @Test
    void sftpClientAuthenticatePassword() throws IOException {
        try (SshClient sshClient = SshClient.setUpDefaultClient()) {
            sshClient.start();

            try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) {
                // パスワード
                clientSession.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(SSH_PASSWORD));
                // ログイン
                clientSession.auth().verify();

                try (SftpClient sftpClient = SftpClientFactory.instance().createSftpClient(clientSession)) {
                    // get
                    try (InputStream is = sftpClient.read("hello-server.txt")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server\n");
                    }

                    try (InputStream is = sftpClient.read("remote-dir/hello-server-in-dir.txt")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server in remote-dir\n");
                    }

                    // put
                    sftpClient.put(Path.of("pom.xml"), "remote-dest-dir/pom.xml");

                    // get
                    try (InputStream is = sftpClient.read("remote-dest-dir/pom.xml")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).contains(
                                "<groupId>org.apache.sshd</groupId>",
                                "<artifactId>sshd-sftp</artifactId>"
                        );
                    }
                }
            }
        }
    }

SftpClientを作成するのに、先ほどまで使っていたClientSessionが必要になります。

                try (SftpClient sftpClient = SftpClientFactory.instance().createSftpClient(clientSession)) {

ファイル受信、送信など。

                    // get
                    try (InputStream is = sftpClient.read("hello-server.txt")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server\n");
                    }

                    try (InputStream is = sftpClient.read("remote-dir/hello-server-in-dir.txt")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server in remote-dir\n");
                    }

                    // put
                    sftpClient.put(Path.of("pom.xml"), "remote-dest-dir/pom.xml");

                    // get
                    try (InputStream is = sftpClient.read("remote-dest-dir/pom.xml")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).contains(
                                "<groupId>org.apache.sshd</groupId>",
                                "<artifactId>sshd-sftp</artifactId>"
                        );
                    }

送信するファイルは、pom.xmlにしました。

SftpClientを取得した後は、そこまで操作に迷うことはないと思います。

公開鍵認証でSSH接続して、SFTP操作を実行

最後は公開鍵認証を使って接続した後に、SFTP操作を行うパターン。

    @Test
    void sftpClientAuthenticateKey() throws IOException, GeneralSecurityException {
        try (SshClient sshClient = SshClient.setUpDefaultClient()) {
            // SSH鍵の設定
            Collection<KeyPair> keys =
                    SecurityUtils
                            .getKeyPairResourceParser()
                            .loadKeyPairs(null, null, FilePasswordProvider.of(SSH_KEY_PASSPHRASE), Files.newBufferedReader(Path.of(SSH_PRIVATE_KEY), StandardCharsets.UTF_8));
            sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys));

            sshClient.start();

            try (ClientSession clientSession = sshClient.connect(SSH_USERNAME, SSH_HOST, SSH_PORT).verify().getClientSession()) {
                // ログイン
                clientSession.auth().verify();

                try (SftpClient sftpClient = SftpClientFactory.instance().createSftpClient(clientSession)) {
                    // get
                    try (InputStream is = sftpClient.read("hello-server.txt")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server\n");
                    }

                    try (InputStream is = sftpClient.read("remote-dir/hello-server-in-dir.txt")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).isEqualTo("Hello from Server in remote-dir\n");
                    }

                    // put
                    sftpClient.put(Path.of("pom.xml"), "remote-dest-dir/pom.xml");

                    try (InputStream is = sftpClient.read("remote-dest-dir/pom.xml")) {
                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
                        is.transferTo(baos);
                        assertThat(baos.toString(StandardCharsets.UTF_8)).contains(
                                "<groupId>org.apache.sshd</groupId>",
                                "<artifactId>sshd-sftp</artifactId>"
                        );
                    }
                }
            }
        }
    }

ここまでくると、新しい要素はありません。

こんなところでしょうか。

おわりに

Apache MINA SSHDSSH/SFTPクライアントを使ってみました。

ドキュメントのコード例に省略箇所が多かったのであまりイメージが掴めず試してみたのですが、まあそこそこハマりました。
まあ、なんとかなりました。

それにしても、Apache MINA自体がもともとネットワーク系のライブラリーだからか、利用側からするとやや低レイヤーな
感じもします。もう少し簡単に書きたい場合はSSHJの方が便利かもですね。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/03/08/190208より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14