编写能够访问Web页面的java应用程序是不难的,这要感谢Java核心库对其所提供的良好支持。但是,说得更加全面一点,任何这样的应用程序都必须支持代理服务器和HTTP验证。幸运的是,从1.2版开始,Java就对验证提供了本地支持。只用小小的努力,你就能给先前的版本加上类似的支持。
使用代理服务器
从技术上讲,代理服务器只是一个接受请求并把这些请求转发到最终目的地或者另一个代理服务器的代理程序。代理服务器的典型应用是实现缓冲和防火墙。
在HTTP这一层次,一个穿过代理服务器的请求和一般的请求没有很大的区别。一般来说,这个请求被送到代理服务器而不是真正的目的地,而且地址被完全记录下来,以便于代理服务器能够找到目标主机。
Java以非凡的系统属性为代理服务器提供支持。你所需要做的就是把http.PRoxyHost属性设置到代理服务器地址,把http.proxyPort设置到代理服务器端口。例如,假如在地址proxy.mycompany.com:8132有一个代理服务器,以下这段代码使用这个代理服务器配置了Java的HTTP协议:
System.getProperties().setProperty( "http.proxyHost", "proxy.mycompany.com" );
System.getProperties().setProperty( "http.proxyPort", "8132" );
对于简单的情况这个支持就够了。但是有些代理,尤其是防火墙,被配置成需要验证,以答应请求通过。在这种情况下,就必须提供验证支持,这就引出了HTTP验证。
HTTP验证
HTTP协议支持对资源的保护,所以必须提供一个合适的验证方法来访问这些资源。当一个请求要对这样的资源进行访问,Web服务器会回应一个401(未经授权的)错误码(见RFC2616),在这种情况下,包含了一个指定方案和域的WWW验证报头。
这个方案定义了提供权限的方法。目前指定了两种策略:基本的和摘要的(见RFC2617)。我会把重点放在基本策略上,因为它更普遍也更轻易实现,尽管摘要策略更加强大且提供更高的安全性。
域
域是一个定义相同主机内受保护区间(一组需要保护的资源)的任意串。单一的主机可以有多个域,相同域内的所有资源都共享相同权限——也就是说,假如对给定资源请求的权限是合法的,那么在相同的区域内,对其他资源的任何后来的请求也必须是合法的。
假设你要访问某个Web服务器上“Protected Territory”内的一个受保护资源,那么会应将包含以下报头(假设Web服务器使用的是基本的验证):
WWW-Authenticate: Basic realm="Protected Territory"
你必须重新发出请求,并包括指定了合法用户名和密码的验证报头。如何提取用户名和密码则是应用程序的工作了。例如浏览器通常使用显示主机和域的对话框来要求输入用户名和密码。
验证报头必须以:的形式,提供所使用的验证方案、用户名和密码(一个base64编码串,见RFC2045)。所以,假如用户名是Alladin(阿拉丁),密码是“open sesame(芝麻开门)”,那么要送出的报头就应该是:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
代理服务器也是这样的,除非代理服务器回应一个407(需要代理服务器验证)错误码和代理服务器验证报头,这个报头是回应一个不包含合法权限的请求的。权限必须在请求的代理服务器验证报头里提供。
代理服务器要在应用程序的配置里设定,用户名和密码也是一样的。使用这个方法,应用程序就不用等待要求必要信息的407错误码,也不用重新发送请求了。
Java里的HTTP验证
在URLConnection类里,Java提供了实现HTTP验证所需的一切。在对服务器的连接打开后(在调用了connect方法后),报头可通过getHeaderField(String)方法访问到,这个方法在给定名字时将报头的值作为一个串返回。
所以在发送一个请求后,使用getHeaderField方法就可以获取WWW验证报头。假如这个方法返回的是空值,那么这个报头就没有被使用,这个请求不需要授权。否则就解析返回的值,获取域,并使用这个值获取用户名和密码。然后重发请求,这一次使用setRequestProperty方法设置验证报头。
你可以不停地重复这一过程,直到授权被许可或者用户中止了操作。你也可以保存用户名和密码以备以后在相同的域内为请求提供权限。根据HTTP验证的规范,所有对相同或者更深层次的相同路径的请求应该被认为是相同区域内的一部分。
尽管标准的Java类没有提供对base64的支持,但是要实现base63不是一件困难的事。有好几个公开可用的工具,见Listing A。
你可以以同样的方式为代理服务器设置权限。使用setRequestProperty方法在每个请求里设置代理服务器验证报头就行了。
要使为Java应用程序加上HTTP验证更加轻易些,我编写了HttpGet类(Listing B),它对代理服务器也提供了支持。完整的API见表A:
表A
最重要的方法是doGet,它对一个URL发出HTTP GET请求,返回一个连接的URLConnection对象。它使用authorize方法来取得指定域的用户名和密码。缺省的方法是使用一个对话框来要求输入这个信息。假如你需要定制一个方法,只用细分HttpGet并取代authorize方法就行了。假如相同的区域要被再次访问,权限可以被缓冲以供再次使用。
Listing C是一个简单的例子,这个例子说明了使用HttpGet类获取指定URL(使用命令行指定)的内容并将其打印到标准输出的过程。
验证和Java 1.2
Java 1.2以及更高的版本以类验证器的形式为验证提供了本地支持。你所需要做的只是将其细分并取代getPassWordAuthentication方法。这个方法必须获取用户名和密码,并将它们作为一个PasswordAuthentication对象返回。
你还必须使用Authenticator.setDefault方法为你验证器工具的实例进行注册。完成注册后,每当碰到受保护资源Java就会调用getPasswordAuthentication方法。
这个方法的一个好处是Java会治理所有的底层细节。除此之外,验证器不仅仅局限于HTTP验证,还能用于其他任何协议。不足之处在于它只在Java 1.2里有。Listing D使用的是Listing C的例子,但是使用了验证器的类。
结论
对代理服务器和HTTP验证的支持是任何应用程序处理Web页面时所必需的。正如我们在这里看到的,Java 1.2对这种验证提供了本地支持,只用一点工作,你就能在任何版本的Java里加入这样的支持。
我的方法说明的只是这个过程的基本点,还有很大空间可供改善。(你可以在此找到本文所设计程序的源代码。)例如,你也许想要增加对其他类型请求的支持,但是这个版本的只能用于GET。在今后的文章里,我会重提这个问题,并讨论如何对其改进。