CLOVER🍀

That was when it all began.

Apache MINA SSHDでSSHSFTPクラむアントを䜿うJavaのSSHクラむアントラむブラリヌ事情

これは、なにをしたくお曞いたもの

最近䜿うこずが枛っおきた気がするSSHクラむアントなどですが、Javaで扱おうずするずJSchが有名かず思いたす。

このあたりの事情を最近芋盎すずちょっず倉わっおいた感じがするのず、今回はApache MINA SSHDでSSHやSFTPの
クラむアントを扱っおみたいず思いたす。

JavaのSSHクラむアントラむブラリヌ

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 SSHDでSSH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 SSHDでSSHSFTPクラむアントを䜿っおみたした。

ドキュメントのコヌド䟋に省略箇所が倚かったのであたりむメヌゞが掎めず詊しおみたのですが、たあそこそこハマりたした。
たあ、なんずかなりたした。

それにしおも、Apache MINA自䜓がもずもずネットワヌク系のラむブラリヌだからか、利甚偎からするずやや䜎レむダヌな
感じもしたす。もう少し簡単に曞きたい堎合はSSHJの方が䟿利かもですね。