通过JSch进行SSH隧道传输

13
我的目标是连接到位于防火墙后面的服务器(主机)。我能够通过连接网络中的另一台服务器(隧道)并SSH到该服务器来访问此服务器。但是,我无法通过JSch实现相同的场景。
我无法使下面的代码工作,这是我为此编写的。请告诉我是否有什么愚蠢的地方。
public class JschExecutor {

    public static void main(String[] args){
        JschExecutor t=new JschExecutor();
        try{
            t.go();
        } catch(Exception ex){
            ex.printStackTrace();
        }
    }
    public void go() throws Exception{

        StringBuilder outputBuffer = new StringBuilder();

        String host="xxx.xxx.xxx.xxx"; // The host to be connected finally
        String user="user";
        String password="passwrd";
        int port=22;

        String tunnelRemoteHost="xx.xx.xx.xx"; // The host from where the tunnel is created

        JSch jsch=new JSch();
        Session session=jsch.getSession(user, host, port);
        session.setPassword(password);
        localUserInfo lui=new localUserInfo();
        session.setUserInfo(lui);
        session.setConfig("StrictHostKeyChecking", "no");

        ProxySOCKS5 proxyTunnel = new ProxySOCKS5(tunnelRemoteHost, 22);
        proxyTunnel.setUserPasswd(user, password);
        session.setProxy(proxyTunnel);

        session.connect(30000);

        Channel channel=session.openChannel("exec");
        ((ChannelExec)channel).setCommand("hostname");

        channel.setInputStream(null);
        ((ChannelExec)channel).setErrStream(System.err);

        InputStream in=channel.getInputStream();
        BufferedReader ebr = new BufferedReader(new InputStreamReader(in));

        channel.connect();

        while (true) {
            byte[] tmpArray=new byte[1024];
            while(in.available()>0){
                int i=in.read(tmpArray, 0, 1024);
                if(i<0)break;
                outputBuffer.append(new String(tmpArray, 0, i)).append("\n");
             }
            if(channel.isClosed()){
                System.out.println("exit-status: "+channel.getExitStatus());
                break;
             }
        }
        ebr.close();

        channel.disconnect();

        session.disconnect();

        System.out.println(outputBuffer.toString());
    }

  class localUserInfo implements UserInfo{
    String passwd;
    public String getPassword(){ return passwd; }
    public boolean promptYesNo(String str){return true;}
    public String getPassphrase(){ return null; }
    public boolean promptPassphrase(String message){return true; }
    public boolean promptPassword(String message){return true;}
    public void showMessage(String message){}
  }     

} 

上述代码在session.connect(30000);一行会出现以下异常。
com.jcraft.jsch.JSchException: ProxySOCKS5: com.jcraft.jsch.JSchException: fail in SOCKS5 proxy
    at com.jcraft.jsch.ProxySOCKS5.connect(ProxySOCKS5.java:317)
    at com.jcraft.jsch.Session.connect(Session.java:231)
    at com.ukris.main.JschExecutor.go(JschExecutor.java:50)
    at com.ukris.main.JschExecutor.main(JschExecutor.java:19)
Caused by: com.jcraft.jsch.JSchException: fail in SOCKS5 proxy
    at com.jcraft.jsch.ProxySOCKS5.connect(ProxySOCKS5.java:200)
    ... 3 more
2个回答

12

在jsch上设置一个SOCKS代理可以让你连接到远程侧正在运行的代理服务器。远程侧的sshd不被认为是SOCKS代理。你需要做的是在本地建立一个端口转发到你要隧道连接的机器上的ssh端口,然后使用api建立到该系统的第二个ssh连接。

我参考了你的示例,并稍微改写了一下来实现这个。

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

public class JschExecutor2 {

    public static void main(String[] args){
        JschExecutor2 t=new JschExecutor2();
        try{
            t.go();
        } catch(Exception ex){
            ex.printStackTrace();
        }
    }

    public void go() throws Exception{

        StringBuilder outputBuffer = new StringBuilder();

        String host="firstsystem"; // First level target
        String user="username";
        String password="firstlevelpassword";
        String tunnelRemoteHost="secondlevelhost"; // The host of the second target
        String secondPassword="targetsystempassword";
        int port=22;


        JSch jsch=new JSch();
        Session session=jsch.getSession(user, host, port);
        session.setPassword(password);
        localUserInfo lui=new localUserInfo();
        session.setUserInfo(lui);
        session.setConfig("StrictHostKeyChecking", "no");
        // create port from 2233 on local system to port 22 on tunnelRemoteHost
        session.setPortForwardingL(2233, tunnelRemoteHost, 22);
        session.connect();
        session.openChannel("direct-tcpip");

        // create a session connected to port 2233 on the local host.
        Session secondSession = jsch.getSession(user, "localhost", 2233);
        secondSession.setPassword(secondPassword);
        secondSession.setUserInfo(lui);
        secondSession.setConfig("StrictHostKeyChecking", "no");

        secondSession.connect(); // now we're connected to the secondary system
        Channel channel=secondSession.openChannel("exec");
        ((ChannelExec)channel).setCommand("hostname");

        channel.setInputStream(null);

        InputStream stdout=channel.getInputStream();

        channel.connect();

        while (true) {
            byte[] tmpArray=new byte[1024];
            while(stdout.available() > 0){
                int i=stdout.read(tmpArray, 0, 1024);
                if(i<0)break;
                outputBuffer.append(new String(tmpArray, 0, i));
             }
            if(channel.isClosed()){
                System.out.println("exit-status: "+channel.getExitStatus());
                break;
             }
        }
        stdout.close();

        channel.disconnect();

        secondSession.disconnect();
        session.disconnect();

        System.out.print(outputBuffer.toString());
    }

  class localUserInfo implements UserInfo{
    String passwd;
    public String getPassword(){ return passwd; }
    public boolean promptYesNo(String str){return true;}
    public String getPassphrase(){ return null; }
    public boolean promptPassphrase(String message){return true; }
    public boolean promptPassword(String message){return true;}
    public void showMessage(String message){}
  }

} 

这段代码的作用是创建一个本地端口转发到目标系统的ssh端口,然后通过它进行连接。运行hostname命令说明它确实在被转发到的系统上运行。

1
这些内容在 openChannel 文档中列出。 - Anya Shenanigans
是的,这与putty中的“隧道”设置相同。在这种情况下,它生成一个本地端口,该端口镜像远程端口,允许您连接到它。如果您要连接的远程端口是SOCKS代理服务器的端口,则可以将Firefox的设置指向此本地端口作为SOCKS代理。它不会以任何方式“SOCKSify”端口,只是简单地向远程端口添加本地镜像。 - Anya Shenanigans
@Petesh 在参考了这些链接12之后,我能够理解你的代码。有两件事需要澄清:(1)第二个URL中提到“尽管代理使用套接字,但它是一个UNIX域套接字,只能从本地文件系统访问。代理不会监听任何基于TCP/IP的套接字。”这与你在打开通道时使用的“direct-tcpip”有什么关系。(2)使用此代码是否存在任何安全隐患? - Dwarrior
@Dwarrior 代理转发是完全不同于TCP端口转发功能的,这与本问题所询问的内容无关。这段代码的安全隐患在哪里?它使用了嵌入式密码,创建了可以被利用的端口转发,因为TCP端口没有固有的访问安全性。这段代码只是简单的示例代码,不应在生产环境中使用。 - Anya Shenanigans
@Petesh - 谢谢您的澄清。这正是我想要理解的。假设您的密码已经被安全地存储,但在获取会话时被解密为实际密码,如果有人想在生产中使用端口转发,那么应该如何处理? - Dwarrior
显示剩余3条评论

0

这已经测试并且正常工作。 这就像安全的管道一样,非常适合隧道传输。

        String strSshUser = "ssh_user_name"; // SSH loging username
        String strSshPassword = "abcd1234"; // SSH login password
        String strSshHost = "your.ssh.hostname.com"; // hostname or ip or
                                                        // SSH server
        int nSshPort = 22; // remote SSH host port number
        String strRemoteHost = "your.database.hostname.com"; // hostname or
                                                                // ip of
                                                                // your
                                                                // database
                                                                // server
        int nLocalPort = 3366; // local port number use to bind SSH tunnel
        int nRemotePort = 3306; // remote port number of your database
        String strDbUser = "db_user_name"; // database loging username
        String strDbPassword = "4321dcba"; // database login password

    final JSch jsch = new JSch();
    Session session = jsch.getSession(strSshUser, strSshHost, 22);
    session.setPassword(strSshPassword);

    final Properties config = new Properties();
    config.put("StrictHostKeyChecking", "no");
    session.setConfig(config);

    session.connect();
    session.setPortForwardingL(nLocalPort, strRemoteHost, nRemotePort);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接