前言背景
相信大家在支付宝、微信、钉钉等各种小程序或支付对接的开发中,经常会遇到服务端回调的问题,至少要求接收回调请求的服务器有公网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
的几个限制:
- 想bind权限端口(1024以内的端口号),必须以root身份连接,普通用户无权限
-
port
参数设置为0
,表示随机监听一个可用的端口 -
bind_address
默认为localhost
,如果为空
或*
或0.0.0.0
则表示bind所有可用ip,但是注意远程服务器的sshd_config
必须启用GatewayPorts
选项才有权限这么做 - 由于ssh是
TCP
协议,因此这个代理也只是TCP
四层反向代理,不能实现UDP
的反向代理
配置
明白这个原理之后,我们就可以在实际应用中实现我们的需求了。由于微信、支付宝、钉钉等绝大多数回调请求都使用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的各种权限。