简介
http代理的实现比较简单,客户端请求发送给代理,代理拿着请求去请求远端服务器,然后将结果返回给客户端,而https无法沿用这个思路,原因是https消息内容是加密的,代理无法解析数据。
查询资料得知,可以利用一项叫http隧道的技术来实现代理。
http协议定义了CONNECT请求方式,该请求方式通常用于开启一个隧道
隧道建立
代理收到CONNECT请求,开始建立与目标服务器之间的代理。一旦建立完成,代理服务器就开始代理进出客户端的tcp流数据
假如我通过代理访问 A 网站,浏览器首先通过 CONNECT 请求,让代理创建一条到 A 网站的 TCP 连接;一旦 TCP 连接建好,代理无脑转发后续流量即可。所以这种代理,理论上适用于任意基于 TCP 的应用层协议,HTTPS 网站使用的 TLS 协议当然也可以。这也是这种代理为什么被称为隧道的原因。对于 HTTPS 来说,客户端透过代理直接跟服务端进行 TLS 握手协商密钥,所以依然是安全的,下图中的抓包信息显示了这种场景
可以看到,浏览器与代理进行 TCP 握手之后,发起了 CONNECT 请求,报文起始行如下:
CONNECT imququ.com:443 HTTP/1.1
对于 CONNECT 请求来说,只是用来让代理创建 TCP 连接,所以只需要提供服务器域名及端口即可,并不需要具体的资源路径。代理收到这样的请求后,需要与服务端建立 TCP 连接,并响应给浏览器这样一个 HTTP 报文:
HTTP/1.1 200 Connection Established
浏览器收到了这个响应报文,就可以认为到服务端的 TCP 连接已经打通,后续直接往这个 TCP 连接写协议数据即可.通过 Wireshark 的 Follow TCP Steam 功能,可以清楚地看到浏览器和代理之间的数据传递:
可以看到,浏览器建立到服务端 TCP 连接产生的 HTTP 往返,完全是明文,这也是为什么 CONNECT 请求只需要提供域名和端口:如果发送了完整 URL、Cookie 等信息,会被中间人一览无余,降低了 HTTPS 的安全性。HTTP 代理承载的 HTTPS 流量,应用数据要等到 TLS 握手成功之后通过 Application Data 协议传输,中间节点无法得知用于流量加密的 master-secret,无法解密数据。而 CONNECT 暴露的域名和端口,对于普通的 HTTPS 请求来说,中间人一样可以拿到(IP 和端口很容易拿到,请求的域名可以通过 DNS Query 或者 TLS Client Hello 中的 Server Name Indication 拿到),所以这种方式并没有增加不安全性。
代码实现
|
|
知识扩展
代理
- 转发代理(隧道或网关)
http tunnel(隧道)
- 反向代理
通常用来控制保护后端服务器,该服务器可能用于负载均衡、认证、加密或缓存
参考资料
用不到 100 行的 Golang 代码实现 HTTP(S) 代理
HTTP 代理原理及实现
http://cizixs.com/2017/03/22/http-tunnel-proxy-and-golang-implementation