Thư viện SSH cho Java [đã đóng]


187

Có ai biết một thư viện tốt để đăng nhập SSH từ Java.


Tôi đã từng sử dụng Trilead SSH nhưng khi tôi kiểm tra trang web ngày hôm nay thì có vẻ như họ đang từ bỏ nó. :( Đó là thứ tôi yêu thích tuyệt đối.
Peter D

1
BTW, có vẻ như Trilead SSH2 đang được duy trì tích cực (vào tháng 10 năm 2013): [ github.com/jenkinsci/trilead-ssh2]
Mike Godin

Trilead SSH2 có một ngã ba tại github.com/connectbot/sshlib
user7610

Câu trả lời:


120

Các Java secure channel (JSCH) là một thư viện rất phổ biến, được sử dụng bởi maven, ant và nhật thực. Nó là mã nguồn mở với giấy phép kiểu BSD.


2
Bạn đã tải xuống nguồn từ sourceforge.net/projects/jsch/files/jsch/jsch-0.1.42.zip/ và chạy "ant javadoc"
David Rabinowitz

73
Tôi đã thử sử dụng JSch một thời gian trước đây và không thể hiểu làm thế nào nó trở nên phổ biến. Nó cung cấp hoàn toàn không có tài liệu (thậm chí không có trong nguồn) và thiết kế API khủng khiếp ( techtavern.wordpress.com/2008/09/30/ Cách tổng hợp khá tốt)
rluba

15
Có Jsch là khủng khiếp, điều này là tốt hơn: github.com/shikhar/sshj
anio

3
Một biến thể của JSch với javadoc cho các phương thức công khai: github.com/ePaul/jsch-documentation
user423430

4
stackoverflow.com/questions/2405885/any-good-jsch-examples/ cám chứa một ví dụ cho việc sử dụng JSCH để chạy các lệnh và nhận đầu ra.
Từ thiện Leschinski

65

Cập nhật: Dự án GSOC và mã ở đó không hoạt động, nhưng đây là: https://github.com/hierynomus/sshj

hierynomus tiếp quản công việc bảo trì từ đầu năm 2015. Đây là liên kết Github cũ hơn, không còn được duy trì:

https://github.com/shikhar/sshj


Có một dự án GSOC:

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

Chất lượng mã có vẻ tốt hơn so với JSch, trong khi, việc triển khai hoàn chỉnh và đang hoạt động, thiếu tài liệu. Trang dự án phát hiện một phiên bản beta sắp tới, cam kết cuối cùng đối với kho lưu trữ là giữa tháng tám.

So sánh các API:

http://code.google.com.vn/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
Cảm ơn! Tôi đã sử dụng mã Apache SSHD (cung cấp API async) làm hạt giống giúp dự án khởi động.
shikhar

1
Tuyệt quá. Tôi đã bắt đầu một dự án bằng cách sử dụng JSch, nhưng tôi thực sự muốn chuyển đổi, nếu tôi nghe phản hồi tích cực hơn về commons-net-ssh.
miku

5
Tôi nên đã đề cập rằng tôi là sinh viên GSOC :)
shikhar

2
vui mừng thông báo github.com/shikhar/sshj - bạn có thể tìm thấy bình ở đó, tìm ra cách lấy nó trên một repo maven
shikhar

1
SFTP của jsch đơn giản hơn nhiều so với những gì được đưa ra ở đây. Có lẽ đây là mã cũ, nhưng chắc chắn nó không phải là một ví dụ về API hiện đại.
Charles Duffy

24

Tôi mới phát hiện ra sshj , dường như có API ngắn gọn hơn nhiều so với JSCH (nhưng nó yêu cầu Java 6). Tài liệu này chủ yếu là bằng các ví dụ tại thời điểm này và thường là đủ để tôi tìm ở nơi khác, nhưng nó có vẻ đủ tốt để tôi thực hiện nó trong một dự án tôi mới bắt đầu.


3
SSHJ thực sự khả thi bởi một người nào đó từ thế giới bên ngoài. JSCH là một mớ hỗn độn của tài liệu và thiết kế API xấu với các phụ thuộc ẩn và phần lớn không thể mã hóa. Trừ khi bạn muốn dành nhiều thời gian để duyệt mã để cố gắng tìm ra điều gì xảy ra, hãy sử dụng SSHJ. (Và tôi ước rằng mình thật khắc nghiệt hoặc không biết gì về JSCH. Tôi thực sự làm thế.)
Robert Fischer

1
Vâng, sshj. Mọi thứ tôi đã thử với nó đều hoạt động: SCP, thực thi quy trình từ xa, chuyển tiếp cổng cục bộ và từ xa, proxy đại lý với jsch-agent-proxy . JSCH là một mớ hỗn độn.
Laurent Caillette

1
Vấn đề với SSHJ là rất khó để thực thi nhiều lệnh. SSHJ có thể là tuyệt vời cho các lệnh quên và quên, nhưng thật khó nếu bạn muốn lập trình tương tác phức tạp hơn. (Tôi chỉ lãng phí nửa ngày cho nó)
bvdb

18

Hãy xem SSHD được phát hành gần đây , dựa trên dự án MINA của Apache.


2
Sử dụng nó, tuy nhiên thiếu tài liệu và ví dụ.
Andreas Mattisson

Có vẻ như thiếu tài liệu đang đưa ra một cách nhẹ nhàng: /
Amalgovinus

5

Có một phiên bản hoàn toàn mới của Jsch trên github: https://github.com/vngx/vngx-jsch Một số cải tiến bao gồm: javadoc toàn diện, hiệu suất nâng cao, xử lý ngoại lệ được cải thiện và tuân thủ thông số RFC tốt hơn. Nếu bạn muốn đóng góp bằng mọi cách xin vui lòng mở một vấn đề hoặc gửi yêu cầu kéo.


4
Quá tệ, đã không có một cam kết mới trong hơn 3 năm nay.
Mike Lowery

0

Tôi lấy câu trả lời của miku và mã ví dụ jsch. Sau đó tôi đã phải tải xuống nhiều tệp trong phiên và giữ nguyên dấu thời gian ban đầu . Đây là mã ví dụ của tôi làm thế nào để làm điều đó, có lẽ nhiều người thấy nó hữu ích. Hãy bỏ qua hàm filenameHack () của chính nó.

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

}

Bạn có thể sử dụng lại một phiên và tránh chi phí kết nối / ngắt kết nối không?
Sridhar Sarnobat

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.