Biblioteka SSH dla Java [zamknięta]


190

Czy ktoś zna dobrą bibliotekę do logowania SSH z Java.


Kiedyś korzystałem z Trilead SSH, ale kiedy dzisiaj sprawdziłem stronę, wygląda na to, że się poddają. :( To był mój absolutnie ulubiony.
Peter D

1
BTW, wygląda na to, że Trilead SSH2 jest aktywnie utrzymywany (w październiku 2013 r.): [ Github.com/jenkinsci/trilead-ssh2]
Mike Godin

Trilead SSH2 ma widelec na github.com/connectbot/sshlib
user7610

Odpowiedzi:


120

Java bezpiecznego kanału (jsch) jest bardzo popularne biblioteki, używane przez maven, Ant i zaćmienia. Jest to oprogramowanie typu open source z licencją typu BSD.


2
Musisz pobrać źródło ze sourceforge.net/projects/jsch/files/jsch/jsch-0.1.42.zip/... i uruchomić „ant javadoc”
David Rabinowitz

73
Próbowałem używać JSch jakiś czas temu i nie mogę zrozumieć, jak stał się tak popularny. Oferuje on absolutnie żadnej dokumentacji (nawet w źródle) i straszny projekt API ( techtavern.wordpress.com/2008/09/30/... podsumowuje to całkiem dobrze)
rluba

15
Tak Jsch jest okropny, to jest lepsze: github.com/shikhar/sshj
anio

3
Wariant JSch z javadoc dla metod publicznych: github.com/ePaul/jsch-documentation
user423430

4
stackoverflow.com/questions/2405885/any-good-jsch-examples/… zawiera przykład użycia JSCH do uruchamiania poleceń i uzyskiwania danych wyjściowych.
Charity Leschinski

65

Aktualizacja: Projekt GSOC i kod tam nie są aktywne, ale jest to: https://github.com/hierynomus/sshj

hierynomus przejął funkcję opiekuna od początku 2015 roku. Oto starszy, nieobsługiwany link Github:

https://github.com/shikhar/sshj


Był projekt GSOC:

http://code.google.com/p/commons-net-ssh/

Jakość kodu wydaje się lepsza niż JSch, który mimo kompletnej i działającej implementacji nie ma dokumentacji. Strona projektu zawiera nadchodzące wydanie beta, ostatnie zatwierdzenie do repozytorium miało miejsce w połowie sierpnia.

Porównaj interfejsy API:

http://code.google.com/p/commons-net-ssh/

    SSHClient ssh = new SSHClient();
    //ssh.useCompression(); 
    ssh.loadKnownHosts();
    ssh.connect("localhost");
    try {
        ssh.authPublickey(System.getProperty("user.name"));
        new SCPDownloadClient(ssh).copy("ten", "/tmp");
    } finally {
        ssh.disconnect();
    }

http://www.jcraft.com/jsch/

Session session = null;
Channel channel = null;

try {

JSch jsch = new JSch();
session = jsch.getSession(username, host, 22);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.setPassword(password);
session.connect();

// exec 'scp -f rfile' remotely
String command = "scp -f " + remoteFilename;
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);

// get I/O streams for remote scp
OutputStream out = channel.getOutputStream();
InputStream in = channel.getInputStream();

channel.connect();

byte[] buf = new byte[1024];

// send '\0'
buf[0] = 0;
out.write(buf, 0, 1);
out.flush();

while (true) {
    int c = checkAck(in);
    if (c != 'C') {
        break;
    }

    // read '0644 '
    in.read(buf, 0, 5);

    long filesize = 0L;
    while (true) {
        if (in.read(buf, 0, 1) < 0) {
            // error
            break;
        }
        if (buf[0] == ' ') {
            break;
        }
        filesize = filesize * 10L + (long) (buf[0] - '0');
    }

    String file = null;
    for (int i = 0;; i++) {
        in.read(buf, i, 1);
        if (buf[i] == (byte) 0x0a) {
            file = new String(buf, 0, i);
            break;
        }
    }

    // send '\0'
    buf[0] = 0;
    out.write(buf, 0, 1);
    out.flush();

    // read a content of lfile
    FileOutputStream fos = null;

    fos = new FileOutputStream(localFilename);
    int foo;
    while (true) {
        if (buf.length < filesize) {
            foo = buf.length;
        } else {
            foo = (int) filesize;
        }
        foo = in.read(buf, 0, foo);
        if (foo < 0) {
            // error
            break;
        }
        fos.write(buf, 0, foo);
        filesize -= foo;
        if (filesize == 0L) {
            break;
        }
    }
    fos.close();
    fos = null;

    if (checkAck(in) != 0) {
        System.exit(0);
    }

    // send '\0'
    buf[0] = 0;
    out.write(buf, 0, 1);
    out.flush();

    channel.disconnect();
    session.disconnect();
}

} catch (JSchException jsche) {
    System.err.println(jsche.getLocalizedMessage());
} catch (IOException ioe) {
    System.err.println(ioe.getLocalizedMessage());
} finally {
    channel.disconnect();
    session.disconnect();
}

}

2
Dzięki! Użyłem kodu Apache SSHD (który oferuje asynchroniczny interfejs API) jako materiału siewnego, co dało początek projektowi.
shikhar

1
Wspaniały. Rozpocząłem projekt przy użyciu JSch, ale naprawdę lubię się przełączać, jeśli słyszę więcej pozytywnych opinii na temat commons-net-ssh.
miku

5
Powinienem wspomnieć, że byłem studentem GSOC :)
shikhar

2
z przyjemnością ogłaszam github.com/shikhar/sshj - możesz tam znaleźć słoik, zastanawiając się, jak go zdobyć w repozytorium maven
shikhar

1
SFTP firmy jsch jest znacznie prostsze niż podane tutaj. Być może jest to stary kod, ale z pewnością nie jest przykładem nowoczesnego interfejsu API.
Charles Duffy

24

Właśnie odkryłem sshj , który wydaje się mieć znacznie bardziej zwięzłe API niż JSCH (ale wymaga Java 6). Dokumentacja jest w tym momencie przeważnie oparta na przykładach w repozytorium i zwykle to wystarczy, żebym szukał gdzie indziej, ale wydaje mi się, że wystarczy mi rzucić okiem na projekt, który właśnie rozpocząłem.


3
SSHJ jest w rzeczywistości wykonalny przez kogoś ze świata zewnętrznego. JSCH to bałagan złej dokumentacji i interfejsu API z ukrytymi i w dużej mierze nieczytelnymi zależnościami. Jeśli nie chcesz spędzać dużo czasu na przeglądaniu kodu, aby dowiedzieć się, co jest grane, użyj SSHJ. (I chciałbym być ostry lub żartobliwy wobec JSCH. Naprawdę.)
Robert Fischer,

1
Tak, sshj. Wszystko, co próbowałem, działało: SCP, zdalne wykonywanie procesu, lokalne i zdalne przekierowywanie portów, proxy agenta z jsch-agent-proxy . JSCH był bałaganem.
Laurent Caillette

1
Problem z SSHJ polega na tym, że bardzo trudno jest wykonać wiele poleceń. SSHJ może być świetny do strzelania i zapominania o komendach, ale jest to ból, jeśli chcesz zaprogramować bardziej skomplikowane interakcje. (Właśnie zmarnowałem na to pół dnia)
bvdb


5

Na github dostępna jest nowa wersja Jsch: https://github.com/vngx/vngx-jsch Niektóre ulepszenia obejmują: kompleksową obsługę javadoc, lepszą wydajność, lepszą obsługę wyjątków i lepsze przestrzeganie specyfikacji RFC. Jeśli chcesz w jakikolwiek sposób przyczynić się, otwórz problem lub wyślij prośbę o wycofanie.


4
Szkoda, że ​​od ponad 3 lat nie było nowego zatwierdzenia.
Mike Lowery,

0

Wziąłem odpowiedź miku i przykładowy kod jsch. Następnie musiałem pobrać wiele plików podczas sesji i zachować oryginalne znaczniki czasu . To jest mój przykładowy kod, jak to zrobić, prawdopodobnie wiele osób uważa to za przydatne. Proszę zignorować funkcję filenameHack () w mojej własnej skrzynce.

package examples;

import com.jcraft.jsch.*;
import java.io.*;
import java.util.*;

public class ScpFrom2 {

    public static void main(String[] args) throws Exception {
        Map<String,String> params = parseParams(args);
        if (params.isEmpty()) {
            System.err.println("usage: java ScpFrom2 "
                    + " user=myid password=mypwd"
                    + " host=myhost.com port=22"
                    + " encoding=<ISO-8859-1,UTF-8,...>"
                    + " \"remotefile1=/some/file.png\""
                    + " \"localfile1=file.png\""
                    + " \"remotefile2=/other/file.txt\""
                    + " \"localfile2=file.txt\""

            );
            return;
        }

        // default values
        if (params.get("port") == null)
            params.put("port", "22");
        if (params.get("encoding") == null)
            params.put("encoding", "ISO-8859-1"); //"UTF-8"

        Session session = null;
        try {
            JSch jsch=new JSch();
            session=jsch.getSession(
                    params.get("user"),  // myuserid
                    params.get("host"),  // my.server.com
                    Integer.parseInt(params.get("port")) // 22
            );
            session.setPassword( params.get("password") );
            session.setConfig("StrictHostKeyChecking", "no"); // do not prompt for server signature

            session.connect();

            // this is exec command and string reply encoding
            String encoding = params.get("encoding");

            int fileIdx=0;
            while(true) {
                fileIdx++;

                String remoteFile = params.get("remotefile"+fileIdx);
                String localFile = params.get("localfile"+fileIdx);
                if (remoteFile == null || remoteFile.equals("")
                        || localFile == null || localFile.equals("") )
                    break;

                remoteFile = filenameHack(remoteFile);
                localFile  = filenameHack(localFile);

                try {
                    downloadFile(session, remoteFile, localFile, encoding);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }

        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
            try{ session.disconnect(); } catch(Exception ex){}
        }
    }

    private static void downloadFile(Session session, 
            String remoteFile, String localFile, String encoding) throws Exception {
        // send exec command: scp -p -f "/some/file.png"
        // -p = read file timestamps
        // -f = From remote to local
        String command = String.format("scp -p -f \"%s\"", remoteFile); 
        System.console().printf("send command: %s%n", command);
        Channel channel=session.openChannel("exec");
        ((ChannelExec)channel).setCommand(command.getBytes(encoding));

        // get I/O streams for remote scp
        byte[] buf=new byte[32*1024];
        OutputStream out=channel.getOutputStream();
        InputStream in=channel.getInputStream();

        channel.connect();

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // reply: T<mtime> 0 <atime> 0\n
        // times are in seconds, since 1970-01-01 00:00:00 UTC 
        int c=checkAck(in);
        if(c!='T')
            throw new IOException("Invalid timestamp reply from server");

        long tsModified = -1; // millis
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(tsModified < 0 && buf[idx]==' ') {
                tsModified = Long.parseLong(new String(buf, 0, idx))*1000;
            } else if(buf[idx]=='\n') {
                break;
            }
        }

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // reply: C0644 <binary length> <filename>\n
        // length is given as a text "621873" bytes
        c=checkAck(in);
        if(c!='C')
            throw new IOException("Invalid filename reply from server");

        in.read(buf, 0, 5); // read '0644 ' bytes

        long filesize=-1;
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(buf[idx]==' ') {
                filesize = Long.parseLong(new String(buf, 0, idx));
                break;
            }
        }

        // read remote filename
        String origFilename=null;
        for(int idx=0; ; idx++){
            in.read(buf, idx, 1);
            if(buf[idx]=='\n') {
                origFilename=new String(buf, 0, idx, encoding); // UTF-8, ISO-8859-1
                break;
            }
        }

        System.console().printf("size=%d, modified=%d, filename=%s%n"
                , filesize, tsModified, origFilename);

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'

        // read binary data, write to local file
        FileOutputStream fos = null;
        try {
            File file = new File(localFile);
            fos = new FileOutputStream(file);
            while(filesize > 0) {
                int read = Math.min(buf.length, (int)filesize);
                read=in.read(buf, 0, read);
                if(read < 0)
                    throw new IOException("Reading data failed");

                fos.write(buf, 0, read);
                filesize -= read;
            }
            fos.close(); // we must close file before updating timestamp
            fos = null;
            if (tsModified > 0)
                file.setLastModified(tsModified);               
        } finally {
            try{ if (fos!=null) fos.close(); } catch(Exception ex){}
        }

        if(checkAck(in) != 0)
            return;

        buf[0]=0; out.write(buf, 0, 1); out.flush(); // send '\0'
        System.out.println("Binary data read");     
    }

    private static int checkAck(InputStream in) throws IOException {
        // b may be 0 for success
        //          1 for error,
        //          2 for fatal error,
        //          -1
        int b=in.read();
        if(b==0) return b;
        else if(b==-1) return b;
        if(b==1 || b==2) {
            StringBuilder sb=new StringBuilder();
            int c;
            do {
                c=in.read();
                sb.append((char)c);
            } while(c!='\n');
            throw new IOException(sb.toString());
        }
        return b;
    }


    /**
     * Parse key=value pairs to hashmap.
     * @param args
     * @return
     */
    private static Map<String,String> parseParams(String[] args) throws Exception {
        Map<String,String> params = new HashMap<String,String>();
        for(String keyval : args) {
            int idx = keyval.indexOf('=');
            params.put(
                    keyval.substring(0, idx),
                    keyval.substring(idx+1)
            );
        }
        return params;
    }

    private static String filenameHack(String filename) {
        // It's difficult reliably pass unicode input parameters 
        // from Java dos command line.
        // This dirty hack is my very own test use case. 
        if (filename.contains("${filename1}"))
            filename = filename.replace("${filename1}", "Korilla ABC ÅÄÖ.txt");
        else if (filename.contains("${filename2}"))
            filename = filename.replace("${filename2}", "test2 ABC ÅÄÖ.txt");           
        return filename;
    }

}

Czy byłeś w stanie ponownie wykorzystać sesję i uniknąć narzutów związanych z łączeniem / rozłączaniem?
Sridhar Sarnobat

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.