2009-06-15 23 views
164

Czy ktoś wie o dobrej bibliotece do logowania SSH z Java.Biblioteka SSH dla Java

+0

Ja używałem Trilead SSH ale gdy sprawdziłem Witryna dzisiaj wydaje się, że rezygnują z niej. :(To był mój absolutny ulubiony: –

+1

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

+2

Spróbuj [jcabi-ssh] (http://ssh.jcabi.com), wyjaśniono tutaj http://www.yegor256.com/2014/09/02/java-ssh-client.html – yegor256

Odpowiedz

105

Java Secure Channel (JSCH) jest bardzo popularną biblioteką, używaną przez maven, mrówkę i zaćmienie. Jest to open source z licencją w stylu BSD.

+1

To wygląda dobrze - czy są tam jakieś javadocs? wszystko?Przykłady są nieco słabo napisanym kodem huśtawki (z krótkiego samplowania, które zrobiłem). –

+2

Chcesz pobrać źródło z https://sourceforge.net/projects/jsch/files/jsch/jsch-0.1.42.zip/download i uruchomić "ant javadoc" –

+60

Próbowałem używać JSch jakiś czas temu i nie rozumiem, jak to się stało tak popularne. Nie oferuje absolutnie żadnej dokumentacji (nawet w źródle) i okropnego projektu API (http://techtavern.wordpress.com/2008/09/30/about-jsch-open-source-project/ podsumowuje to całkiem dobrze) – rluba

16

Spójrz na niedawno wydany SSHD, który jest oparty na projekcie Apache MINA.

+1

Używając go, brakuje jednak dokumentacji i przykładów. –

55

Aktualizacja: Projekt GSoC a kod nie jest aktywny, ale to jest: https://github.com/hierynomus/sshj

hierynomus objął stanowisko opiekuna od początku 2015. Oto starsza, nie jest już rozwijany, Github Link:

https://github.com/shikhar/sshj


było projekt GSoC:

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

Jakość kodu wydaje się lepsza niż JSch, który, mimo że jest kompletny i działający, brakuje dokumentacji. Strona projektu dostrzega nadchodzące wydanie beta, ostatnie zatwierdzenie do repozytorium miało miejsce w połowie sierpnia.

Porównaj 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 async API) jako seed, który dał projektowi kickstart. – shikhar

+1

Świetnie. Zacząłem projekt używając JSch, ale bardzo lubię się przełączać, gdy słyszę więcej pozytywnych opinii na temat commons-net-ssh. – miku

+3

Powinienem wspomnieć, że byłem uczniem GSOC :) – shikhar

20

Właśnie odkryłem sshj, który wydaje się mieć dużo bardziej zwięzły API niż jsch (ale to wymaga Java 6). Dokumentacja jest w większości przypadków przykładowa w repozytorium i zazwyczaj wystarcza mi to, aby zajrzeć gdzie indziej, ale wydaje mi się, że wystarczy, że zrobię z tego projekt, który właśnie rozpocząłem.

+1

SSHJ jest faktycznie możliwe do zrealizowania przez kogoś ze świata zewnętrznego. JSCH to bałagan złej dokumentacji i projektowania interfejsu API z ukrytymi i w dużej mierze nieczytelnymi zależnościami. Jeśli nie chcesz spędzać dużo czasu na przechodzeniu kodu, aby dowiedzieć się, co jest grane, użyj SSHJ. (I szkoda, że ​​nie byłem szorstki lub kpiący z JSCH.) Naprawdę.) –

+0

Tak, sshj. Wszystko, co wypróbowałem, działało: SCP, zdalne wykonywanie procesu, lokalne i zdalne przekazywanie portów, agent proxy z [jsch-agent-proxy] (http://www.jcraft.com/jsch-agent-proxy). JSCH był bałagan. –

5

Istnieje nowa wersja Jscha na github: https://github.com/vngx/vngx-jsch Niektóre ulepszenia obejmują: wszechstronny javadoc, zwiększoną wydajność, ulepszoną obsługę wyjątków i lepszą zgodność specyfikacji RFC. Jeśli chcesz w jakikolwiek sposób wnieść swój wkład, proszę otworzyć problem lub wysłać żądanie wyciągnięcia.

+2

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

0

Wziąłem odpowiedź miku i kod przykładowy 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ć, pewnie wiele osób uzna to za przydatne. Proszę zignorować funkcję filenameHack(), która jest moją własną aplikacją.

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; 
    } 

} 
+0

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