Java 용 SSH 라이브러리 [닫기]


190

누구나 Java에서 SSH 로그인하기에 좋은 라이브러리를 알고 있습니다.


나는 Trilead SSH를 사용했지만 오늘 사이트를 확인했을 때 포기하고있는 것 같습니다. : ((이것은 내가 가장 좋아하는 것입니다.)
Peter D

1
BTW, Trilead SSH2가 활발하게 유지되고있는 것으로 보입니다 (2013 년 10 월) : [ github.com/jenkinsci/trilead-ssh2]
Mike Godin

Trilead SSH2은 포크가 github.com/connectbot/sshlib
user7610

답변:


120

자바 채널 (JSCH)이 보안 받는다는, 개미와 이클립스에 의해 사용되는, 매우 인기있는 라이브러리입니다. BSD 스타일 라이센스가있는 오픈 소스입니다.


2
당신은에서 소스 다운로드 EED sourceforge.net/projects/jsch/files/jsch/jsch-0.1.42.zip/... 실행 "개미의 javadoc"
데이비드 라 비노 위츠

73
얼마 전에 JSch를 사용해 보았는데 어떻게 인기를 얻었는지 이해할 수 없습니다. 이 문서는 전혀 제공하지 않으며 (소스 내에서도) 끔찍한 API 디자인 ( techtavern.wordpress.com/2008/09/30/… 요약)
rluba

15
예 Jsch는 끔찍합니다. github.com/shikhar/sshj
anio

3
공공 메소드의 javadoc와 JSch의 변형 : github.com/ePaul/jsch-documentation
user423430

4
stackoverflow.com/questions/2405885/any-good-jsch-examples/… 에는 JSCH를 사용하여 명령을 실행하고 출력을 얻는 예제가 포함되어 있습니다.
자선 레친 스키

65

업데이트 : GSOC 프로젝트와 코드가 활성화되어 있지 않지만 https://github.com/hierynomus/sshj

hierynomus는 2015 년 초부터 관리자로 인수되었습니다. 더 오래되고 더 이상 유지 관리되지 않는 Github 링크는 다음과 같습니다.

https://github.com/shikhar/sshj


GSOC 프로젝트가있었습니다 :

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

코드 품질은 JSch보다 낫습니다. JSch는 완벽하고 작동하는 구현이지만 문서가 부족합니다. 프로젝트 페이지는 다가오는 베타 릴리스를 발견했으며 저장소에 대한 마지막 커밋은 8 월 중순입니다.

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
감사! 프로젝트를 킥 스타트하게하는 시드로 Apache SSHD 코드 (비동기 API 제공)를 사용했습니다.
shikhar

1
큰. JSch를 사용하여 프로젝트를 시작했지만 commons-net-ssh에 대한 긍정적 인 피드백이 들리면 전환하고 싶습니다.
miku

5
나는 내가 GSOC 학생 :라고 언급한다
shikhar

2
github.com/shikhar/sshj 를 발표하게되어 기쁩니다. jarven 이 maven repo에 도착하는 방법을 알아낼 수 있습니다
shikhar

1
jsch의 SFTP는 여기에 제공된 것보다 훨씬 간단합니다. 아마도 이것은 오래된 코드 일 것입니다. 그러나 현대 API의 예는 아닙니다.
Charles Duffy

24

방금 sshj를 발견 했는데 JSCH 보다 훨씬 간결한 API를 가지고있는 것 같습니다 (그러나 Java 6이 필요합니다). 이 시점에서 문서는 대부분 리포지토리 예제로 작성되었으며 대개 다른 곳을보기에 충분하지만 방금 시작한 프로젝트에 대해 설명하는 것으로 충분합니다.


3
SSHJ는 실제로 외부 세계의 누군가가 실행할 수 있습니다. JSCH는 숨겨진 문서와 해독 할 수없는 의존성을 가진 잘못된 문서와 API 디자인의 혼란입니다. 무슨 일인지 알아 내기 위해 코드를 살펴 보는 데 많은 시간을 소비하지 않으려면 SSHJ를 사용하십시오. (그리고 나는 JSCH에 대해 가혹하거나 면밀했으면 좋겠다. 정말로 그렇다.)
Robert Fischer

1
예, sshj. 내가 시도한 모든 것은 SCP, 원격 프로세스 실행, 로컬 및 원격 포트 전달, jsch-agent-proxy를 사용한 에이전트 프록시 입니다. JSCH는 엉망이었습니다.
Laurent Caillette

1
SSHJ의 문제점은 여러 명령을 실행하기가 매우 어렵다는 것입니다. SSHJ는 명령을 잊고 잊어 버릴 수 있지만 더 복잡한 상호 작용을 프로그래밍하려는 경우 고통 스럽습니다. (방금 반나절을 낭비했습니다)
bvdb

18

Apache MINA 프로젝트를 기반으로하는 가장 최근에 출시 된 SSHD를 살펴보십시오 .


2
그러나 그것을 사용하면 문서와 예제가 부족합니다.
Andreas Mattisson

문서의 부족처럼 보인다는 가볍게두고있다 : /
Amalgovinus

5

GitHub의에 Jsch 최대의 새로운 버전이 있습니다 : https://github.com/vngx/vngx-jsch 개선 중 일부는 다음과 같습니다 : 포괄적 인 자바 독, 향상된 성능, 개선 된 예외 처리, 그리고 더 나은 RFC 사양 준수. 어떤 방식 으로든 기여하고 싶다면 이슈를 열거 나 풀 요청을 보내십시오.


4
3 년 넘게 새로운 커밋이 없었던 것은 너무 나빴습니다.
Mike Lowery

0

miku의 답변과 jsch 예제 코드를 사용했습니다. 그런 다음 세션 중에 여러 파일다운로드 하고 원래 타임 스탬프를 유지해야했습니다 . 이것은 내 코드 예제입니다. 아마도 많은 사람들이 유용하다고 생각합니다. filenameHack () 함수 자체의 유스 케이스를 무시하십시오.

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

}

세션을 재사용하고 연결 / 연결 끊김의 오버 헤드를 피할 수 있었습니까?
Sridhar Sarnobat 2016 년

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.