ssh隧道和JSch实现

2,615 阅读5分钟

什么是ssh?

ssh(secure shell)是一种对数据进行加密安全传输的协议。 利用ssh工具(比如iTerm2)可以非常方便的登录远程提供有ssh服务的主机,也可以很方便的进行文件传输。

什么是ssh隧道,为什么需要打隧道?

ssh隧道就是ssh tunnel,利用 ssh tunnel 可以进行端口转发(port forwarding), 它在ssh连接上建立一个加密的通道。创建了ssh tunnel之后,可以突破一些网络的限制访问不能直接访问的资源。 ssh tunnel分为三种:本地(L),远程(R)和动态(D)。

使用场景

客户端clientC在公司内部使用的是内网IP,而服务器serverA和serverB在IDC机房或者在云服务器商那边。服务器serverA有公网IP能与客户端clientC正常通信,而服务器serverB没有公网IP(又或者没有暴露80端口到公网),不能与客户端clientC直接通信,但是服务器serverA和服务器serverB是可以通过内网进行通信的。现在我们需要通过clientC访问serverB的相关端口。

网络关系图

通过posman无法直接调用到server B上的服务

postman访问超时

解决方案

1. 登录目标服务器:通过跳板机serverA ssh登录到目标服务器serverB之后,用curl命令调用localhost接口。
  • 缺点:
    • 输入两次密码,必须每次登录到跳板机
    • curl调用参数较多,难以保存和修改
2. 使用ssh tunnel

常用命令详解

-L port:host:hostport 将本地机(客户机)的某个端口转发到远端指定机器的指定端口. 工作原理是这样的, 本地机器上分配了一个 socket 侦听 port 端口, 一旦这个端口上有了连接, 该连接就经过安全通道转发出去, 同时远程主机和 host 的 hostport 端口建立连接. 可以在配置文件中指定端口的转发. 只有 root 才能转发特权端口. IPv6 地址用另一种格式说明: port/host/hostport

-R port:host:hostport 将远程主机(服务器)的某个端口转发到本地端指定机器的指定端口. 工作原理是这样的, 远程主机上分配了一个 socket 侦听 port 端口, 一旦这个端口上有了连接, 该连接就经过安全通道转向出去, 同时本地主机和 host 的 hostport 端口建立连接. 可以在配置文件中指定端口的转发. 只有用 root 登录远程主机才能转发特权端口. IPv6 地址用另一种格式说明: port/host/hostport

-D port 指定一个本地机器 “动态的’’ 应用程序端口转发. 工作原理是这样的, 本地机器上分配了一个 socket 侦听 port 端口, 一旦这个端口上有了连接, 该连接就经过安全通道转发出去, 根据应用程序的协议可以判断出远程主机将和哪里连接. 目前支持 SOCKS4 协议, 将充当 SOCKS4 服务器. 只有 root 才能转发特权端口. 可以在配置文件中指定动态端口的转发.

我的实现

使用ssh -L命令,实现本地端口转发。注意:创建隧道前先确保已将跳板机(serverA)的ssh公钥上传至目标服务器(serverB)。

原理图

ssh -N -L 9999:remoteIP:remotePort root@jumperIP

jumper就是跳板机serverA, remote就是提供服务的serverB

  • 缺点:
  • 每次都要密码
  • 一直要保证iTerm2窗口打开,有些服务器设置了idle超时,就无法持续访问。
  • 没办法实现自动化接口测试

再(更)进(懒)一(一)步(点)

问:这样每次我必须手动输入ssh命令,并且输入跳板机密码,能不能用代码实现? 答:可以!用JSch!

什么是JSch?

官网:www.jcraft.com/jsch/ 简介:JSch是一个SSH2的纯Java实现,可以让你使用连接到sshd服务器,并且使用端口转发,X11转发,文件传输等功能。 你还可以集成这些功能到你的Java程序中,并且是BSD开源软件。(BSD开源协议是一个给于使用者很大自由的协议。基本上使用者可以”为所欲为”,可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。)

实战使用

  1. 添加Maven依赖
<dependency>
    <groupId>com.jcraft</groupId>
    <artifactId>jsch</artifactId>
    <version>0.1.54</version>
</dependency>
  1. 编写测试类Tunnel
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;

/**
 * Created by taojiaju on 2017/5/26.
 */
public class Tunnel {
    public static void main(String[] args) {
        Tunnel t = new Tunnel();
        try {
            t.go();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void go() throws Exception {
        //跳板机(图中的serverA),需要输入用户名、密码
        String host = "XXX.XXX.XXX.XXX";
        String user = "username";
        String password = "password";
        int port = 22; //ssh默认端口22
        
        // 目标服务器(图中的serverB)
        int tunnelLocalPort = 9999; //本地服务器转发的端口
        String tunnelRemoteHost = "YYY.YYY.YYY.YYY";
        int tunnelRemotePort = 80; //目标服务器服务访问的端口

        JSch jsch = new JSch();
        Session session = jsch.getSession(user, host, port);
        session.setPassword(password);
        localUserInfo lui = new localUserInfo();
        session.setUserInfo(lui);
        session.connect();
        session.setPortForwardingL(tunnelLocalPort, tunnelRemoteHost, tunnelRemotePort);
        System.out.println("Connected");
    }

    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 tunnel的远程http接口调用

通过JSch连接上之后,我就可以通过HttpClient调用目标机上的接口了,妈妈再也不用担心啦。

        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httppost = new HttpPost("http://localhost:9999/your/interface/position");
        httppost.setHeader("Content-type", "application/json");
        httppost.setHeader("Accept", "application/json;charset=UTF-8");
        String result = "";
        String requestStr = "{\"taskName\":\"test\", \"taskParameter\":{\"extend\":{\"operateDate\": \"2017-05-25 14:10:32\", \"userIds\": \"123456\" }}}";
        try {
            StringEntity entity = new StringEntity(requestStr, "UTF-8");
            entity.setContentEncoding("UTF-8");
            httppost.setEntity(entity);
            HttpResponse response = httpClient.execute(httppost);
            if (response.getStatusLine().getStatusCode() == 200) {
                //读返回数据
                result = EntityUtils.toString(response.getEntity());
                System.out.println(result);
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        }
        // 关闭session
        session.disconnect();

blog.creke.net/722.html www.linuxdiyf.com/linux/17234… codelife.me/blog/2012/1… www.jianshu.com/p/90c10a242… www.beanizer.org/site/index.…