DTeam 团队日志

Doer、Delivery、Dream

使用SSH代理在本地开发环境调试各种回调

冯宇 Posted at — May 10, 2018 阅读

前言背景

相信大家在支付宝、微信、钉钉等各种小程序或支付对接的开发中,经常会遇到服务端回调的问题,至少要求接收回调请求的服务器有公网IP,以便能收到请求,微信的开发甚至要求回调接口必须是https,而本地开发环境往往都是内网环境,甚至连固定ip都没有,难道每次测试回调内容只能发布到测试环境中去测试,有没有简单的方法呢?

当然有!答案就是使用SSH代理(或其他类似的proxy代理)。假定发布应用给公网用户,肯定得有一台固定ip的公网服务器吧,甚至还有固定ip的测试环境,利用这台服务器做ssh server实现代理(Linux服务器自带ssh server,windows环境可以安装ssh server,这里不赘述了)。

之前我写过一篇利用SSH代理访问内网资源的文章,如果之前的文章对运维比较有用,那么本篇的内容体现了ssh对开发的价值。之前的文章-R参数不是重点,那么本篇则是详解-R参数。-L参数代表在本地监听端口,将请求转发到远程,而-R参数正好相反,在远程监听一个端口,将请求转发到本地。

原理讲解

本地必须有openssh client(Linux和Mac OS自带,windows 10系统目前也自带,windows低版本操作系统需要自己单独下载openssh client安装),一台远程服务器,有公网ip,开启ssh server,用作回调服务器。

本地需要通过ssh连接远程服务器,在远程服务器上监听一个端口,将此端口的数据转发到本地的某个应用端口上,实现将远程数据代理到本地的作用,大概的示意图像这样:

request ----> | remote server | <------------> | local client |
                                        ^
                                      ssh通道

这样的话本地客户端不需要有固定ip,只要能ssh连接到remote server,就可以让remote server将request转发过来,而openssh恰好就提供了这样的功能,对应的是-R参数:

     -R [bind_address:]port:host:hostport
     -R [bind_address:]port:local_socket
     -R remote_socket:host:hostport
     -R remote_socket:local_socket
     -R [bind_address:]port

按照官方man手册的叙述,-R参数的作用是在远程服务器bind一个ip:port(或unix domain socket),监听请求,并将请求转发到本地的ip:port(或unix domain socket)上,本质上还是一个反向代理。

因此这个命令就非常容易写出来(以下命令在本地执行):

ssh -R 9999:127.0.0.1:9999 user@remote

和常见的ssh user@remote这种直接连接ssh shell的命令相比,添加了一个-R参数,代表ssh连接成功之后,在远程服务器上监听127.0.0.1:9999(注意省略bind_address代表bind环回口),将请求转发到127.0.0.1:9999上(相对于本机的Ip),因此就等于访问了本机127.0.0.1:9999上监听的应用程序。如果不想登录到远程shell,可以追加-N参数,如果不想挂起在前台,可以追加-f参数,如果想启用传输压缩(gzip),还可以追加-C参数。更多有用的参数可以参见man手册

需要注意下-R的几个限制:

配置

明白这个原理之后,我们就可以在实际应用中实现我们的需求了。由于微信、支付宝、钉钉等绝大多数回调请求都使用HTTP协议,这个协议本身是工作在TCP协议之上的,因此可以用ssh代理实现代理HTTP协议。

对于http请求,其实我们只要在sshd_config启用GatewayPorts选项,去bind 0.0.0.0就可以将回调请求转发到本地了,但是考虑到https反向代理,以及其他的一些http预处理需求,实际使用的时候建议前面放一个Nginx或其他http proxy server做反向代理,这样的话就不需要配置sshd_config,只bind 127.0.0.1就够了。

以我们刚才的配置为例,监听远程服务器的127.0.0.1:9999端口,因此很容易写出Nginx的反向代理配置:

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://127.0.0.1:9999;
    }
}

把域名A记录指向remote server就行了(比如api.example.com),这样回调请求到http://api.example.com/callback就会转发到本地的http://127.0.0.1:9999/callback,于是乎在本地开发环境就可以愉快的调试回调请求了,再也不用反复发布到测试环境调试那么麻烦了。

微信端回调要求https,那么也简单,使用免费的Let’s encrypt证书,或者其他已有的安全证书,配置到Nginx就行了,使得https://api.example.com可以使用就行了,这里就不再赘述了。

ssh直接做可能会因为网络闪断导致代理失效,如果希望ssh自动重连,可以使用autossh这个小工具,帮我们在ssh断掉之后自动重连,维持代理隧道的稳定。

autossh的使用也十分简单,基本兼容绝大多数ssh参数,像ssh一样用就行,比如:

autossh -M 0 -R 9999:192.168.1.10:9999 -C -N

更详细的用法参考官方文档或者man手册即可。

结语

本篇文章我们介绍了ssh的-R代理在实际开发过程中的用途,结合之前利用SSH代理访问内网资源这篇文章,SSH的代理功能就全部讲解完毕了。可能会有人要问,ssh功能如此强大,为了防止ssh被滥用,我如何限制ssh的权限呢?对于这个问题,我将在后续的分享中详解一下ssh的各种权限。