[Java]リモートのサーバにSSH接続してコマンドを実行する[JSch]

2016.12.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Javaでリモートのサーバのシェルスクリプトを実行する、という処理を書く機会がありました。
使い道は、、、そうないかもですが。(TeraTerm使えばいいしな)
考えられるとしたらWebシステムを使ってブラウザからリモートのサーバのシェル実行したい場合とかでしょうか。

検証環境

プログラム実行側

  • OS:Windows8
  • Java:1.8.0_101
  • 接続先サーバOS

  • Amazon Linux AMI release 2016.09
  • CentOS 7
  • JSchを使用する

    使用したのはJSchというライブラリです。
    このライブラリを使うことでJavaでSSH接続を行えます。
    実装していたプロジェクトではGradleを使ってましたので、Gradleを使用した場合の方法です。
    dependenciesに以下を追加してください。※2016年12月6日時点での最新版は0.1.54でした。

    compile "com.jcraft:jsch:0.1.54"

    ビルドツール使わない場合、こちらからjarファイルをダウンロードしてきます。

    JSchを使ってこんな感じで実装しました。

    import java.io.BufferedInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.Closeable;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    
    import com.jcraft.jsch.ChannelExec;
    import com.jcraft.jsch.JSch;
    import com.jcraft.jsch.JSchException;
    import com.jcraft.jsch.Session;
    
    public class RemoteShellExecutor implements Closeable {
    
    	private Session session;
    
    	private ChannelExec channel;
    
    	/**
    	 * コンストラクタ
    	 * @param host
    	 * @param userName
    	 * @param password
    	 * @param port
    	 * @throws Exception
    	 */
    	public RemoteShellExecutor (String host, String userName, String password, int port) {
    		try {
    			JSch jsch = new JSch();
    			jsch.setKnownHosts("C:\\Users\\yura\\.ssh\\known_hosts");
    			session = jsch.getSession(userName, host, port);
    			session.setPassword(password);
    			session.connect();
    			channel = (ChannelExec)session.openChannel("exec");
    		} catch (JSchException e) {
    			// 例外時の処理
    		}
    	}
    
        /**
         * コマンドを実行する。
         * @param command
         * @return 処理結果
         * @throws IOException
         * @throws JSchException
         */
        public int execute(String command) throws Exception {
        	// コマンド実行する。
        	this.channel.setCommand(command);
        	channel.connect();
        	// エラーメッセージ用Stream
            BufferedInputStream errStream = new BufferedInputStream(channel.getErrStream());
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buf = new byte[1024];
            while (true) {
                int len = errStream.read(buf);
                if (len <= 0) {
                    break;
                }
                outputStream.write(buf, 0, len);
            }
            // エラーメッセージ取得する
            String message = new String(outputStream.toByteArray(), StandardCharsets.UTF_8);
            channel.disconnect();
            // コマンドの戻り値を取得する
            int returnCode = channel.getExitStatus();
            return  returnCode;
        }
    
    	@Override
    	public void close() {
    		session.disconnect();
    	}
    }

    こんな感じでシェルを実行します。

    try (RemoteShellExecutor executor = new RemoteShellExecutor("host", "userName", "password", 22)) {
    	int returnCode = executor.execute("/home/yura/test.sh");
    } catch (Exception e) {
    	// 例外処理
    }

    ほとんど参考にしたコードそのままですが。
    変えた所はjsch.setKnownHosts()でknown_hostsのパスを指定してあげたところと、channel.getExitStatus()でシェルの戻り値を取得しているところくらいでしょうか。
    known_hostsには対象のサーバーの公開鍵を追記しておく必要があります。
    シェルの戻り値とエラーメッセージは、エラーログに残しておいたりとか、リトライ処理とか追加する場合に使用するといいと思います。

    鍵認証したい場合

    パスワード認証ではなく、鍵認証を行いたいという場合は、このコードでいえば30行目あたりに以下のコードを追加すれば、パスワードと同様に接続をすることができます。

    jsch.addIdentity("キーの場所のパス");

    おわりに

    JavaでSSH接続するプログラムを書く必要がある場合は、JSchを使うと覚えておいていただけるとよいかと思います。
    JSchはこれ以外にもSCP、SFTPでのファイル転送もできたりするようです。
    コマンド実行よりはそちらの方で使う事の方が多そうな気もしますので、機会があればそちらも試してみようかなと思います。

    参考

    JCraft
    Qiita Java SSHでコマンド実行