天天看點

Java Tip 96: 使用 HTTPS 編寫用戶端程式

Java Tip 96: 使用 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.www.protocol"); 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位址中找到它們的相關資訊:http://forum.java.sun.com/[email protected]@.787ad8de.

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.www.protocol"); 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的站點上找到:

    http://www.servlets.com

  • 你也可以下載下傳

    HttpsMessage

    類源碼的壓縮檔案:

    HttpsMessage.zip

  • 以下是若幹用于測試HTTPS通訊的網頁位址:
    • https://www.verisign.com/
    • https://happiness.dhs.org/
    • https://www.microsoft.com
    • https://www.sun.com
    • https://www.ftc.gov
  • 更多關于JSSE、可下載下傳位和安裝指令的資訊可以在Sun公司的站點上找到:

    http://java.sun.com/products/jsse/.

  • 關于如何使用一些JSSE服務的描述,包括上文所提到的方法,都可以在O'Reilly網站上Jonathan Knudsen 所編寫的"Secure Networking in Java"中找到:

    http://java.oreilly.com/bite-size/java_1099.html

  • 更多關于

    WininetStreamHandlerFactory

    類的資訊可以在微軟的JSDK文檔中找到:http://www.microsoft.com/java/sdk/。此外,Microsoft knowledge base還出版了"PRB: Allowing the URL class to access HTTPS in Applications":

    http://support.microsoft.com/support/kb/articles/Q191/1/20.ASP

  • 關于在Java 2插件中使用HTTPS的更多資訊可以在Sun站點的"How HTTPS Works in Java Plug-In "中找到:

    http://java.sun.com/products/plugin/1.2/docs/https.html