SSH してみる (パスワード認証編)

前回 サーバを停止する際に利用した SSH がとっても面白そうだったので Wake on LAN は ちょっと置いておいて JavaFXSSH してみることに...

とりあえず、今回は SSH を使う上で絶対に必要になる 認証部分の実装から...
認証方式は 前回同様 一番手っ取り早い パスワード認証 です。
もちろん、今回は ユーザがパスワードを入力できるように ちゃんと認証画面も作ります。
折角なので CSS を使ってこんな感じに。


もちろん、今回も SSH 用のライブラリとして JSch を使っています。
JSch は EclipseNetBeans でも使われており GUI アプリケーションとしても実績があるようなので ちょっと安心ですね。

早速 画面デザインをいい感じで作成しましょう。
当然、今回も NetBeans JavaFX Composer のお世話になることに...
今回は次のように4つの状態を作成しました。

init : 初期状態 (何も表示しない)

auth : 認証要求 (認証ダイアログを表示)

login : 認証中 (プログレスインジケータを表示)

retry : 認証再要求 (ユーザ名変更不可の認証ダイアログを表示)

状態毎に作成した紙芝居だけでも それらしく見えてしまうからすごい。
前はこれをコードだけで作成していたから結構大変だったけど 今じゃ 何の説明もなくても これぐらいなら 5分もかからない...
これも NetBeans JavaFX Composer のおかげですね。

デザインができたところで いよいよ 今回のメインである SSH の認証処理の実装です。
まずは ユーザからの入力を JSch の認証処理に渡す部分を実装します。
それを実装するのが UserInfo インターフェースです。
今回の実装は パスワード認証だけなので

  • promptPassword(String): Boolean
  • getPassword(): String
だけを実装すればよいです。
実装はこんな感じ...
class MyUserInfo extends UserInfo {

    override function promptPassword(message : String) : Boolean {

        // 認証ダイアログでクリックされたボタンの値
        selectedValue = -1;    // OK: 1  キャンセル: 0

        // 認証ダイアログ 表示
        currentState.actual = currentState.findIndex("retry");

        // 認証ダイアログのボタンがクリックされるまで待機
        while (true) {
            Thread.sleep(100);
            if (selectedValue != -1) break;
        }

        // OK ボタンが押されたら true
        return selectedValue == 1;
    }

  // ユーザ名 テキストボックスの値を返す。
    function getUsername(): String { 
        usernameTextBox.text;
    }

    // パスワードボックスの値を返す。
    override function getPassword () : String {
        passwordBox.text;
    }

    // 今回は利用しない
    override function promptYesNo(message: String): Boolean { true; }
    override function showMessage(message: String): Void {}
    override function getPassphrase(): String { null; }
    override function promptPassphrase(message: String): Boolean { true; }
}

promptPassword(String): Boolean は 認証が失敗してもう一度 認証する際に呼び出されます。
このメソッドの戻り値により 認証するかキャンセルするかが決まります。

  • true なら getPassword() でパスワードを取得して認証。
  • false なら 認証処理はキャンセル。
今回は 状態を "retry" に変更して ボタンが押されるまで待機しているだけです。

次は 上記の UserInfo の実装を使って『SSH のセッションを取得してサーバに接続する』 処理の実装です。

// Generated Code


def host = "192.168.188.128";
def port = 22;

def jsch     = new JSch();
def userInfo = new MyUserInfo();

// 認証ダイアログでクリックされたボタンの値
var selectedValue = -1;     // OK: 1  キャンセル: 0

// 非同期で SSH のセッションを取得するためのタスク
ActionTask {
    action: function() {

        // 認証ダイアログ表示
        currentState.actual = currentState.findIndex("auth");

        // 認証ダイアログのボタンがクリックされるまで待機
        while (true) {
            Thread.sleep(100);
            if (selectedValue != -1) break;
        }

        // OK でなければ終了
        if (selectedValue != 1) {
            throw new JSchException("Auth cancel");
        }

        // セッション取得
        def session = jsch.getSession(userInfo.getUsername(), host, port);
        session.setConfig("StrictHostKeyChecking", "no");
        session.setPassword(userInfo.getPassword());
        session.setUserInfo(userInfo);

        // アプリケーション終了時にセッションを切断
        FX.addShutdownAction(function(): Void {
            session.disconnect();
        });

        // サーバに接続
        session.connect(5000);

        // 初期画面 表示
        currentState.actual = currentState.findIndex("init");
        Alert.inform("Auth success");
    }
    onException: function(e) {
        currentState.actual = currentState.findIndex("init");
        Alert.inform(e.getMessage());
    }
}.start();

前回は何も考えずに EDT (Event Dispatch Thread) で処理していましたが、 もちろん 非同期で処理しないと画面がフリーズします。

と言うことで...
今回は過去に実装した ActionTask を使って 非同期で処理することに...
ActionTask は JavaFX でとっても簡単に非同期処理を実現するための自作クラスです。
詳細については 過去の記事を参照してみてください。

SSH のセッションを取得してサーバに接続する処理は ActionTask の action 関数に実装しています。
画面の状態を遷移させるための実装を除けば 前回とほとんど同じです。
やっていることはとっても簡単で コード中にも書いてありますが

  • 状態を "auth" にして 認証ダイアログを表示する。
  • 認証ダイアログのボタンがクリックされるまで ひたすら待つ。
  • OK ボタンがクリックされたら セッションを取得してサーバに接続する。
  • 接続が成功したら 状態を "init" に変更してダイアログを表示する。
とたったこれだけです。

後は、認証ダイアログのボタンがクリックされた際に実行されるアクションを実装するだけ...
アクションといっても

  • OK ボタンがクリックされた時は "login" 状態へ
  • キャンセルボタンがクリックされた時は "init" 状態へ
それぞれ変更するだけなのです。
function loginButtonAction(): Void {
    selectedValue = 1;
    currentState.actual = currentState.findIndex("login");
}

function cancelButtonAction(): Void {
    selectedValue = 0;
    currentState.actual = currentState.findIndex("init");
}

今回のサンプルアプリケーションは SSH サーバを設定する機能がないため (コード直書きのため) 公開しても実行できないので とりあえず今回は説明だけ...