Java HTTP&Socksv5 隧道代理方案

北战南征 提交于 2019-12-06 16:38:39

一.名词解释

代理服务器:

通常意义上说的是,通过第三方途径到达目标服务器,以及和目标服务器通信的手段。这种服务器同时具有客户端和服务端的属性,客户端用来和目标服务器交互服务端用来和客户端交互

Tunnel:

隧道的意思,本篇指的是基于TLS/SSL的隧道代理通道

HTTP&SOCKS v5:

HTTP和Socks v5不同于基于网络硬件的VPN技术,而是在应用层和传输层上实现的协议。对于非Tunnel模式下,前者是基于HTTP实现的透明代理,后者Socks v5直接基于TCP/UDP协议实现,也就是说,后者可以完成更多网络代理,如FTP,SSH,MAIL,SOCKET,HTTP等代理。前者可以实现的,后者完全可以实现,后者可以用在各种环境中,比如代理QQ聊天等,代理HTTP的ShadowSocks。

HTTP透明代理:

HTTP可以代理非CONNECT的GET,POST,PUT,DELETE,OPTIONS等请求,这种称为透明代理,实现起来非常容易。

SOCKS v5透明代理:

默认情况下,SOCKS v5直接通过自身协议,而不通过TLS/SSL进行数据交换的问题。

 

二.HTTP&SOCKS v5隧道问题

HTTP&SOCKS v5隧道实质上说的是代理HTTPS或者其他TLS/SSL的网络请求,透明代理的实现如下。

[客户端] ----------------------[代理服务器]----------------------[目标服务器]

隧道代理比透明代理多了一个环节,那就是TLS/SSL安全层密钥交换。这类有个问题就是密钥交换。

HTTP CONNECT与SOCKS v5 TLS/SSL代理如下:

[客户端] ----------<tls/ssl>-------[代理服务器]------<tls/ssl>-------[目标服务器]

问题描述:

目标服务器---->客户端 可以直接进行密钥交换,但是如果中间出现1-N个代理服务器,那么目标服务器---->客户端的密钥无法交换。同样因为证书必须是端到端的验证,因此,即便通过代理将服务端证书传递给客户端,那么依然是无效的证书。还有一个问题是TLS/SSL握手不可传递

 

三.解决方案

我们不需要将目标服务器证书传递给代理服务器,我们只需要在交互的过程中,代理服务器作为服务端和客户端直接进行TLS/SSL交互,代理服务器作为客户端和目标服务器进行TLS/SSL交互,也就是说,最终目标服务器和客户端证书没有实质关联。

[ [客户端] ----------<tls/ssl - 1 >-------[代理服务器 作为SSL服务器端]]

[ [代理服务器作为SSL客户端]------<tls/ssl - 2 >-------[目标服务器]]

也就是说,最终,客户端使用的是代理服务器的证书,代理服务器使用了目标服务器的证书,两种证书没有联系,但是传输却是不可篡改的,而且是有效的。

四.实现原理

4.1代理服务器客户端通道问题的解决

最近写了一篇博客《Java 实现TLS/SSL证书的自动安装校验》,我们实现了模拟浏览器实现CA自动安装和校验。开篇我们说过,代理服务器同时具有客户端和服务器端的属性,而我们的《Java 实现TLS/SSL证书的自动安装校验》实际上用在客户端属性中。当然,我们还有另一种最简单的方法,就是全部信任所有服务器端的证书,但是这种方法的缺陷是交互性能较低。

4.2代理服务器客户端通道问题的解决

我们需要为代理服务器生成自签名的Root CA和Intermediate(中间人)CA,然后签名客户端要访问的域名。签名完成之后,升级Socket->SSLSocket,并且设置为服务端

SSLSocket secureSocket = (SSLSocket)sslFactory.createSocket(socketClient, socketClient.getInetAddress().getHostName(), socketClient.getPort(), true);
secureSocket.setUseClientMode(false);

 

五.附加内容

DNS Resover问题
代理实现需要频繁的域名解析,可能遇到DNS问题。

为了实现快速的域名解析,我们建议使用DNS域名解析xbill DNS,这是一个开源的DNS查询和管理工具

Lookup lookup = new Lookup("www.baidu.com",Type.A);
Record[] records = lookup.run();
System.out.println(Arrays.asList(records));

当然,如果你有自己的DNS服务器,并且对自己的DNS服务器信心满满,也可以使用如下代码段查询DNS

public static final String RECORD_A = "A";

	public static List lookup(String hostName, String record) {

		List result = new Vector();
		try {
			Hashtable env = new Hashtable();
			env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
//添加DNS服务器地址
			env.put("java.naming.provider.url","dns://114.114.114.114");//
			InitialDirContext ictx = new InitialDirContext(env);
			
			Hashtable<?, ?> environment = ictx.getEnvironment();
			System.out.println(environment);
			
			Attributes attrs = ictx.getAttributes(hostName, new String[] { record });
			Attribute attr = attrs.get(record);

			NamingEnumeration attrEnum = attr.getAll();
			while (attrEnum.hasMoreElements())
			{
					result.add(attrEnum.next());
			}
		} catch (NamingException e) {
			e.printStackTrace();
		} catch (NullPointerException e) {
			e.printStackTrace();
		}
		return result;
	}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!