使用 HTTPS 編寫客戶端程序

使用 HTTPS 編寫客戶端程序
如何在標準 URL 類中使用 HTTPS 協議

By Matt Towers
摘要
使用 HTTPS(Hypertext Transfer Protocol Secure 安全超文本傳輸協議)並非你所想的那樣簡單直接。如果你曾經嘗試在 Java 客戶端和 HTTPS 服務器之間進行安全的通訊,也許會注意到標準的 java.net.URL 類並不支持 HTTPS協議。這篇文章將向你展示,如何使用 JDK 1.2-compatible 虛擬機或微軟的 JDK 1.1-compatible JView 來克服這些限制。
如果你曾經嘗試在 Java 客戶機和 HTTPS(安全超文本傳輸協議)服務器之間進行安全的通訊,也許會注意到標準的 java.net.URL 類並不支持 HTTPS 協議。服務端解決此問題的方法是非常簡單明瞭的。因爲現今幾乎所有的Web服務器都使用 HTTPS 協議來提供查詢數據的機制。一旦配置好你的服務器,任何瀏覽器只要簡單地將 URL 地址中的協議指定成 HTTPS ,就能夠在你的服務器上安全地進行信息查詢。如果你沒有搭建起 HTTPS 服務器,則可以在互聯網上幾乎所有 HTTPS 網頁中測試你的客戶端代碼。在資料部分給出了一個列表,裏面列出若干可供你進行 HTTPS 通訊測試的服務器地址。
然而從客戶端的角度來看,在熟悉的 HTTP 後面簡單的加上“S”就能夠安全通信。這種簡單性充滿了迷惑性。事實上,瀏覽器在後臺做了大量的工作,以保證沒有任何人篡改或你所發送的請求數據。然而 HTTPS 協議用來加密的算法是 RSA Security 所擁有的專利(這種狀況至少還要持續幾個月)。該加密算法得到了瀏覽器製造商的許可,但 Sum Microsystems 公司卻不同意將它綁定到標準的 Java URL 類實現中。這就導致當你創建 URL 對象時,若將協議指定爲 HTTPS,就會拋出一個 MalformedURLException 異常。

幸運的是,爲了解決這個侷限,Java規格說明書提供爲 URL 類選擇一個代替的流句柄的能力。然而當你使用不同的虛擬機( virtual machine )時,此技術的實現方法也是不同的。在微軟的 JDK 1.1-compatible 虛擬機 JView 中,微軟許可該加密算法並提供了一個 HTTPS 流句柄作爲它的 wininet 包的一部分。而SUN最近爲它的 JDK 1.2-compatible 虛擬機發布了 Java Secure Sockets Extension(JSSE),在 JSSE 裏許可並提供了 HTTPS 流句柄。本文將具體闡述如何使用 JSSE 和微軟的 wininet 包來實現 HTTPS 流句柄。

JDK 1.2-compatible 虛擬機
在 JDK 1.2-compatible 虛擬機中使用 HTTPS 的技術主要依賴於 Java Secure Sockets Extension(JSSE)1.0.1 版本。你必須先安裝 JSSE 並且將它添加到客戶端虛擬機的類路徑中,才能夠使用這項技術。

當你安裝好 JSSE 之後,你必須設置一項系統屬性,將一個新的安全提供者添加到 Security 類對象。完成這項要求有若干種方法。鑑於這篇文章的目的,在這裏介紹一種實用方法:

System.setProperty("java.protocol.handler.pkgs",
"com.sun.net.ssl.internal.[url]www.protocol[/url]");
Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());


完成以上兩個函數調用之後,運行如下所示代碼時將不再拋出 MalformedURLException 異常:

URL url = new URL("https://[your server]");


如果連接標準的SSL端口443,可以忽略在URL字符串的後面添加端口號這一選項。但倘若你所連接的的Web服務器採用非標準端口進行SSL通訊,就需要像下面這樣,在URL字符串後面添加該端口號:

URL url = new URL("https://[your server]:7002");


有些服務器擁有的可能是無簽名或非法的 SSL 證明書。倘若涉及這類服務器的URL,使用此方法就需要注意。這種情況下,如果試圖從URL的連接對象中檢索輸入或輸出流時(例如運行以下代碼),就會拋出一個 SSLException 異常,並顯示 "untrusted server cer chain." 消息。如果該服務器擁有合法且有簽名的證明書,則不會拋出任何異常。

URL url = new URL("https://[your server]");
URLConnection con = URL.openConnection();
//SSLException thrown here if server certificate is invalid
con.getInputStream();


最顯而易見的解決上述問題的方法就是爲你的服務器取得一個簽名證明書。而 Java Developer Connectin forums 中爲此問題成立了一個工作區,可以在這個URL地址中找到它們的相關信息:[url]http://forum.java.sun.com/forum?14@@.787ad8de.[/url]

Microsoft JView
由於微軟和Sun公司關於在 Windows 平臺上使用 Java 的許可問題有爭論,微軟的 JView 虛擬機現在僅僅是基於 JDK 1.1-compliant 的。而 JSSE 需要 1.2.2-compatible 或以上版本的虛擬機,因此上述的方法並不適用於在 JView 上運行的客戶機。但是微軟同樣也提供了足夠方便的 HTTPS 流句柄,將其作爲 com.ms.net.wininet 包的一部分。

在 JView 環境中,只要爲 URL 類調用一個簡單靜態函數就能夠設置 HTTPS 流句柄:

URL.setURLStreamHandlerFactory(new
com.ms.net.wininet.WininetStreamHandlerFactory());


執行以上的函數調用之後,再運行下面的代碼就不會拋出 MalformedURLException 異常:

URL url = new URL("https://[your server]");


使用這種方法需要注意兩點。首先,根據JDK的文檔,serURLStreamHandlerFactory 函數在一個虛擬機上最多隻能被調用一次。之後的調用將會產生 Error。其次,正如在1.2虛擬機解決方案中所說,使用那些指向無簽名或非法證明書的服務器的 URL 時必須要謹慎。同前所述,這種情況下試圖向該 URL 地址的連接對象檢索輸入或輸出數據流時,就會出問題。不過微軟的流句柄拋出的是一個標準 IOExceptiony 異常,而不是 SSLException。

URL url = new URL("https://[your server]");
URLConnection con = url.openConnection();
//IOException thrown here if server certificate is invalid
con.getInputStream();


同樣,解決此問題最顯而易見的方法就是僅和那些擁有簽名和合法證明書的服務器進行通訊。不過JView還提供了另外一個選擇。將要向 URL 連接目標檢索輸入輸出流之前,你可以先爲 connection 對象調用 setAllowUserInteraction(true) 函數。JView 在運行時就會顯示消息,向用戶警告該服務器的證明書是非法的,用戶可以選擇是否繼續。始終要記住的是,這樣的消息在桌面應用程序中是合乎情理的,但是除了調試的目的外,讓你的服務器任何情況下都彈出消息框可能是不可取的。

注意:你也可以在 JDK 1.2-compatible 版本的虛擬機中調用 setAllowUserInteraction() 函數。不過,在Sun的1.2虛擬機上(測試以下代碼),即使將該函數的參數設成true,也不會顯示消息框。

URL url = new URL("https://[your server]");
URLConnection con = url.openConnection();
//causes the VM to display a dialog when connecting
//to untrusted servers
con.setAllowUserInteraction(true);
con.getInputStream();


在 Windows NT4.0 ,Windows2000 和 Windows9x 操作系統中,com.ms.net.wininet 包被缺省安裝到系統的類路徑下。此外,根據微軟的JDK文檔,WinInetStreamHandlerFactory 是"… the same handler that is installed by default when running applets.",即運行applet時,同樣的流句柄也會被缺省安裝。

平臺獨立性
儘管上述的兩種方法覆蓋了大部分Java客戶程序可能運行的平臺,你的Java客戶程序也許需要在 JDK 1.1 和 JDK 1.2-compliant 虛擬機上都可以正確運行。"寫一次,在任何地方運行,"還記得嗎?很自然會想到將這兩種方法結合起來,根據運行的虛擬機執行相應的處理句柄。下面的代碼展示了一種達到此目的的方法:

String strVendor = System.getProperty("java.vendor");
String strVersion = System.getProperty("java.version");
//Assumes a system version string of the form:
//[major].[minor].[release] (eg. 1.2.2)
Double dVersion = new Double(strVersion.substring(0, 3));

//If we are running in a MS environment, use the MS stream handler.
if( -1 < strVendor.indexOf("Microsoft") )
{
try
{
Class clsFactory =
Class.forName("com.ms.net.wininet.WininetStreamHandlerFactory" );
if ( null != clsFactory )
URL.setURLStreamHandlerFactory(
(URLStreamHandlerFactory)clsFactory.newInstance());
}
catch( ClassNotFoundException cfe )
{
throw new Exception("Unable to load the Microsoft SSL " +
"stream handler. Check classpath." + cfe.toString());
}
//If the stream handler factory has
//already been successfully set
//make sure our flag is set and eat the error
catch( Error err ){m_bStreamHandlerSet = true;}
}
//If we are in a normal Java environment,
//try to use the JSSE handler.
//NOTE: JSSE requires 1.2 or better
else if( 1.2 <= dVersion.doubleValue() )
{
System.setProperty("java.protocol.handler.pkgs",
"com.sun.net.ssl.internal.[url]www.protocol[/url]");
try
{
//if we have the JSSE provider available,
//and it has not already been
//set, add it as a new provide to the Security class.
Class clsFactory = Class.forName("com.sun.net.ssl.internal.ssl.Provider");
if( (null != clsFactory) && (null == Security.getProvider("SunJSSE")) )
Security.addProvider((Provider)clsFactory.newInstance());
}
catch( ClassNotFoundException cfe )
{
throw new Exception("Unable to load the JSSE SSL stream handler." +
"Check classpath." + cfe.toString());
}
}


關於applets
在 applet 中進行基於 HTTPS 的通訊,看起來似乎是上述內容的自然擴展。事實上,在大多數情況下applet中的HTTPS通訊更易於實現。在 Netscape Navigator 和 Internet Explorer 的4.0或更高版本中,它們各自的虛擬機都缺省許可HTTPS協議。因此,倘若你要在applet代碼中創建一個HTTPS連接,只要在創建 URL實例時將協議名稱指定爲"HTTPS"便可。

URL url = new URL("https://[your server]");


如果客戶端瀏覽器運行的是Sun公司的Java 2插件,那麼當你使用 HTTPS 時還會遇到一些其他限制。關於在Java 2插件中使用 HTTPS 的詳細討論可以在Sun公司站點上找到(參看本文末的資料)。

結論
在應用程序中使用 HTTPS 協議,是一種快速而高效地在通訊中獲得足夠的安全性的方法。不幸的是,更多地出於法律而不是技術方面的原因,它沒有被標準 Java 規格說明書所支持。無論如何,隨着 JSSE 的產生以及微軟 com.ms.net.wininet 包的使用,在大多數的平臺上只需要少許幾行代碼就能夠實現安全通訊。


關於作者
Matt Towers, 自稱爲eBozo,最近離開了他在Visio的職位。此後加入華盛頓西雅圖的一個互聯網公司PredictPoint.com ,在那裏從事全職的Java開發工作。

資料

在本文中所描述的跨平臺實現代碼,是在一個叫做HttpsMessage 類中實現的。HttpsMessage 是HttpMessage 類的子類。HttpMessage 類的作者是Jason Hunter,即Java Servlet Programming(O'Reilly & Associates) 一書的作者. 在他即將出版的該書第二版中,你可以找到HttpsMessage 類。如果想要繼承此類,必須下載並安裝com.oreily.servlets 包。這個包以及相關子源代碼可以在Hunter的站點上找到:
[url]http://www.servlets.com[/url]
你也可以下載HttpsMessage 類源碼的壓縮文件:
HttpsMessage.zip
以下是若干用於測試HTTPS通訊的網頁地址:

[url]https://www.verisign.com/[/url]
[url]https://happiness.dhs.org/[/url]
[url]https://www.microsoft.com[/url]
[url]https://www.sun.com[/url]
[url]https://www.ftc.gov[/url]
更多關於JSSE、可下載位和安裝指令的信息可以在Sun公司的站點上找到:
[url]http://java.sun.com/products/jsse/.[/url]
關於如何使用一些JSSE服務的描述,包括上文所提到的方法,都可以在O'Reilly網站上Jonathan Knudsen 所編寫的"Secure Networking in Java"中找到:
[url]http://java.oreilly.com/bite-size/java_1099.html[/url]
更多關於WininetStreamHandlerFactory 類的信息可以在微軟的JSDK文檔中找到:[url]http://www.microsoft.com/java/sdk/[/url]。此外,Microsoft knowledge base還出版了"PRB: Allowing the URL class to access HTTPS in Applications":
[url]http://support.microsoft.com/support/kb/articles/Q191/1/20.ASP[/url]
關於在Java 2插件中使用HTTPS的更多信息可以在Sun站點的"How HTTPS Works in Java Plug-In "中找到:
[url]http://java.sun.com/products/plugin/1.2/docs/https.html[/url]

發佈了48 篇原創文章 · 獲贊 0 · 訪問量 2103
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章