將數(shù)據(jù)提交到網(wǎng)頁服務(wù)器
在上面的例子中,數(shù)據(jù)是作為 URL 的一部分被送到服務(wù)器的,使用的 GET 方法。現(xiàn)在來看一個使用 POST 方法發(fā)送數(shù)據(jù)的例子。這個例子中,http://www./cgi-bin 中的 CGI 腳本 (名為 .cgi) 需要 name 和 email 值。如果用戶提交 Sally McDonald 作為 name 值,smc@yahoo.com 作為 email 值,CGI 腳本會獲取輸入并對消息進(jìn)行解析、解碼,再將提交的內(nèi)容返回給客戶端。這個 CGI 腳本做事情并不多,但我們要用它來演示如何向服務(wù)器提交數(shù)據(jù)。
還有一點非常重要——請注意使用 POST 方法時,消息內(nèi)容的類型是 application/x-www-form-urlencoded,這種類型會:
- 指定常規(guī)數(shù)據(jù)編碼
- 將空格轉(zhuǎn)換為加號 (+)
- 將非文本內(nèi)容轉(zhuǎn)換成十六進(jìn)制數(shù)后接百分號 (%) 的形式
- 在每個 name=value 對之間放置 & 符號
根據(jù)這個編碼規(guī)則,消息 (name=Sally McDonald and email=smc@yahoo.com) 在發(fā)送給 CGI 腳本之前必須被編碼成:
name=Sally+McDonald&email=smc@yahoo.com CGI 腳本收到這個已編碼的消息后會對它進(jìn)行解碼。不過非常幸運,你不必為手工進(jìn)行編碼操作。你可以使用 java.net.URLEncoder 類來對消息進(jìn)行編碼,就像下面的例子中那樣。相應(yīng)地你可以使用 java.net.URLDecoder 來對消息進(jìn)行解碼。
示例代碼 5 中是這個例子的實現(xiàn) (使用 HttpURLConnection 類),它展示了如何使用 POST 方法向服務(wù)器發(fā)送數(shù)據(jù)。你會看到:
- 為 CGI 腳本打開連接和 I/O 流
- 設(shè)置請求方法為 POST
- 使用 URLEncoder.encode 方法對消息進(jìn)行編碼 (URLDecoder.decode 方法可以用于解碼)
- 向 CGI 腳本發(fā)送已經(jīng)編碼的消息
- 接收服務(wù)器返回的消息并在控制臺打印出來
示例代碼 5:PostExample.java
import java.io.*; import java.net.*;
public class PostExample { public static void main(String[] argv) throws Exception { URL url = new URL("http://www./cgi-bin/names.cgi"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); PrintWriter out = new PrintWriter(connection.getOutputStream()); // encode the message String name = "name="+URLEncoder.encode("Qusay Mahmoud", "UTF-8"); String email = "email="+URLEncoder.encode("qmahmoud@", "UTF-8"); // send the encoded message out.println(name+"&"+email); out.close(); BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line; while ((line = in.readLine()) != null) { System.out.println(line); } in.close(); } }
代理服務(wù)器和防火墻
如果你使用了防火墻,你就得把代理服務(wù)器以及端口號的詳細(xì)信息告訴 Java,這樣才能訪問到防火墻外的主機(jī)。你可以通過定義一些 HTTP 或者 FTP 屬性來做到:
http.proxyHost (default: ) http.proxyPort (default: 80 if http.proxyHost specified) http.nonProxyHosts (default: ) http.proxyHost 和 http.proxyPort 用來指定 HTTP 協(xié)議處理器需要使用的代理服務(wù)器和端口號。http.nonProxyHosts 用來指定哪些主機(jī)是直接連接的 (即不通過代理服務(wù)器來連接)。http.nonProxyHosts 屬性的值是一個由 | 分隔開的主機(jī)列表,它可以使用正則表達(dá)式來表示所匹配的主機(jī),如:*.sfbay. 將匹配 sfbay 域中的任何主機(jī)。
ftp.proxyHost (default: ) ftp.proxyPort (default: 80 if ftp.proxyHost specified) ftp.nonProxyHosts (default: ) ftp.proxyHost 和 ftp.proxyPort 用來指定 FTP 協(xié)議處理器需要使用的代理服務(wù)器和端口號。ftp.nonProxyHosts 用來指定哪些主機(jī)是直接聯(lián)系的,指定的方法與 http.nonProxyHosts 類似。
你可以在應(yīng)用程序啟動的時候設(shè)置這些屬性:
Prompt> java -Dhttp.proxyHost=HostName -Dhttp.proxyPort=PortNumber yourApp
HTTP 驗證
HTTP 協(xié)議提供驗證機(jī)制來保護(hù)資源。當(dāng)一個請求要求取得受保護(hù)的資源時,網(wǎng)頁服務(wù)器回應(yīng)一個 401 Unauthorized error 錯誤碼。這個回應(yīng)包含一個指定了驗證方法和領(lǐng)域的 WWW-Authenticate 頭信息。把這個領(lǐng)域想像成一個存儲著用戶名和密碼的數(shù)據(jù)庫,它將被用來標(biāo)識受保護(hù)資源的有效的用戶。比如,你試著去訪問某個網(wǎng)站上標(biāo)識為“Personal Files”的資源,服務(wù)器響應(yīng)可能是:WWW-Authenticate: Basic realm="Personal Files" (假設(shè)驗證方法是 Basic)。
驗證方法
現(xiàn)在有幾種用于網(wǎng)絡(luò)應(yīng)用的驗證方法,其中最廣泛使用的是基本驗證 (Basic Authentication) 和摘要驗證 (Digest Authentication)。
當(dāng)用戶想要訪問有限的資源時,使用基本驗證方法的網(wǎng)頁服務(wù)器會要求瀏覽器詢顯示一個對話框,并要求用戶輸入用戶名和密碼。如果用戶輸入的用戶名和密碼正確,服務(wù)器就允許他訪問這些資源;否則,在接連三次嘗試失敗之后,會顯示一個錯誤消息頁面。這個方法的缺點是用戶名和密碼都是用 Base64 編碼 (全是可讀文本) 之后傳輸過去的。也就是說,這個驗證方法的安全程度只是和 Telnet 一樣,并不是非常安全。
數(shù)據(jù)驗證方法不會在網(wǎng)絡(luò)中傳輸密碼,而是生成一些數(shù)字 (根據(jù)密碼和其它一些需要的數(shù)據(jù)產(chǎn)生的) 來代替密碼,而這些數(shù)字是經(jīng)過 MD5 (Message Digest Algorithm) 加密的。生成的值在網(wǎng)絡(luò)有隨著服務(wù)器需要用來校難密碼的其它信息一起傳輸。這個方法明顯更為安全。
基于表單的驗證方法和基本驗證方法類似,只是服務(wù)器使用你自定義的登錄頁面來代替了標(biāo)準(zhǔn)的登錄對話框。
最后,客戶證書驗證使用 SLL (Secure Socket Layer,安全套接層) 和客戶證明。
在 Tomcat 下保護(hù)資源
你可以在 tomcat-users.xml 文件中寫一個用戶及其角色的列表。這個文件在 TOMCAT_HOME (你安裝 Tomcat 的目錄) 下的 conf 目錄中。這個文件默認(rèn)包含了三個用戶 (tomcat、role1、both) 的定義。下面一段 XML 代碼是我添加了兩個新用戶 (qusay 和 reader) 之后的 tomcat-users.xml:
<tomcat-users> <user name="tomcat" password="tomcat" roles="tomcat" /> <user name="role1" password="tomcat" roles="role1" /> <user name="both" password= "tomcat" roles="tomcat,role1" /> <user name="qusay" password="guesswhat" roles="author" /> <user name="reader" password="youguess" roles="reader" /> </tomcat-users> 新添加的兩個用戶 (qusay 和 reader) 的 roles 分別設(shè)置為 author 和 reader。角色屬性非常重要,因為當(dāng)你創(chuàng)建安全規(guī)則的時候,每個受限制的資源都是與可訪問它的角色相關(guān)聯(lián) (稍后你會看到)。
下面做個實驗 (假設(shè)你已經(jīng)安裝并配置好了 Tomcat)。為你期望的頁應(yīng)用程序建立一個目錄。可以按下列步驟做好準(zhǔn)備:
- 在你安裝了 Tomcat 的目錄下,有一個目錄是 webapps。在這個目錄下建立一個目錄 (如:learn)。
- 在第一步建立的目錄下建立一個子目錄,命名為 chapter。
- 在 chapter 目錄中,建立一個 HTML 文件,內(nèi)容自定,文件名為 index.html。
- 在第一步建立的目錄下建立一個名為 WEB-INF 的子目錄。
- 在 WEB-INF 目錄中創(chuàng)建一個名為 web.xml 的文件,該文件內(nèi)容如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java./dtd/web-app_2_3.dtd">
<web-app>
<description> Learning Web Programming </description> <security-constraint> <web-resource-collection> <web-resource-name> Restricted Area </web-resource-name> <url-pattern>/chapter/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>tomcat</role-name> <role-name>author</role-name> <role-name>reader</role-name> </auth-constraint> </security-constraint>
<login-config> <auth-method>BASIC</auth-method> <realm-name>Authenticate yourself</realm-name> </login-config>
</web-app>
web.xml 配置描述
web.xml 是描述配置的文件,這里集中說明一下安全相關(guān)的配置元素。
- <security-constraint>:這個元素限制對一個或者多個資源的訪問,可以在配置信息中出現(xiàn)多次。上面的配置信息中,它限制了 chapter 目錄 (http://localhost:8080/learn/chapter) 下所有資源的訪問。<security-constraint> 包含了下列元素:
- <web-resource-collection>:這個元素用于標(biāo)識你想限制訪問的資源。你可以定義 URL 模式 和 HTTP 方法 (用 <http-method> 元素定義 HTTP 方法)。如果沒有定義 HTTP 方法,那么限制將應(yīng)用于所有方法。在上面的應(yīng)用中,我想限制訪問的資源是 http://localhost:8080/learn/chapter/*,也就是 chapter 目錄下的所有文檔。
- <auth-constraint>:這個元素可以訪問上面定義的受限資源的用戶角色。在上面的應(yīng)用中,tomcat、author 和 erader 這三個角色可以訪問這些資源。
- <login-config>:這個元素用于指定驗證方法。它包含下列元素:
- <auth-method>:指定驗證方法。它的值可能是下列值集中的一個:BASIC (基本驗證)、DIGEST (摘要驗證)、FORM (基于表單的驗證) 或者 CLIENT-CERT (客戶證書驗證)。
- <realm-name>:如果選用 BASIC 方法進(jìn)行驗證的時候,標(biāo)準(zhǔn)登錄對話框中的一個描述名稱。
示例
上述配置中使用了 BASIC 驗證方法。下面我們做個實驗:啟動你的 Tomcat 服務(wù)器并向它發(fā)送到 http://localhost:8080/learn/chapter 的請求。這時候,就會有像圖 3 所示那樣的對話框提示你輸入用戶和密碼:
 圖 3:HTTP 基本驗證 (Basic Authentication)
輸入一個用戶及其密碼 (你可以看看 tomcat-users.xml 文件),這個用戶的角色應(yīng)該在配置 (web.xml) 中存在。如果你輸入的用戶名和密碼正確,你就能訪問到那些資源;否則,你還可以再試兩次。
使用摘要驗證來實驗:
- 關(guān)閉你的 Tomcat 服務(wù)器。
- 修改你的配置文件 (web.xml),把 BASIC 換成 DIGEST。
- 重新啟動你的 Tomcat 服務(wù)器。
- 打開一個新的瀏覽器窗口。
- 在地址欄中輸入 http://localhost:8080/learn/chapter 并回車。
你會看到類似的對話框。從圖 4 你可以看到,這個登錄對話框是安全的,因為使用了摘要驗證。
 圖 4:HTTP 摘要驗證 (Digest Authentication)
服務(wù)器在幕后的回復(fù)
當(dāng)使用基本驗證方法保護(hù)資源的時候,服務(wù)器發(fā)回類似于圖 5 所示的響應(yīng)信息:
 圖 5:服務(wù)器回復(fù) (基本驗證)
如果是使用的摘要驗證方法來保護(hù)的資源,服務(wù)器發(fā)回的響應(yīng)信息就像圖 6 所示的那樣:
 圖 6:服務(wù)器回復(fù) (摘要驗證)
Java 支持 HTTP 驗證
J2SE (1.2 或者更高版本) 通過 Authenticator 類為驗證提供了本地支持。你所要做的只是繼承這個類并實現(xiàn)它的 getPasswordAuthentication 方法。這個方法取得用戶名和密碼并用它們生成一個 PasswordAuthentication 對象返回。完成之后,你還得使用 Authenticator.setDefault 方法注冊你的 Authenticator 實例?,F(xiàn)在,只要你想訪問受保護(hù)的資源,就會調(diào)用 getPasswordAuthentication。Authenticator 類管理著所有低層的詳細(xì)資料。它不受 HTTP 的限制,可以應(yīng)用于所有網(wǎng)絡(luò)連接,這不能不說是一個好消息。
示例代碼 6 中是實現(xiàn) Authenticator 的一個示例。正如你所看到的,在請求驗證的時候,getPasswordAuthentication 方法會彈出一個登錄對話框。
示例代碼 6:AuthImpl.java
import java.net.*; import java.awt.*; import javax.swing.*;
public class AuthImpl extends Authenticator { protected PasswordAuthentication getPasswordAuthentication() { JTextField username = new JTextField(); JTextField password = new JPasswordField(); JPanel panel = new JPanel(new GridLayout(2,2)); panel.add(new JLabel("User Name")); panel.add(username); panel.add(new JLabel("Password") ); panel.add(password); int option = JOptionPane.showConfirmDialog(null, new Object[] { "Site: "+getRequestingHost(), "Realm: "+getRequestingPrompt(), panel}, "Enter Network Password", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); if ( option == JOptionPane.OK_OPTION ) { String user = username.getText(); char pass[] = password.getText().toCharArray(); return new PasswordAuthentication(user, pass); } else { return null; } } } 示例代碼 7 用來做測試的代碼。我做的第一件事情就是用 Authenticator.setDefault 讓我的 Authenticator 實例開始運行。我不必去解析任何服務(wù)器返回的信息以檢查是否需要驗證,因為 Authenticator 類非常聰明,它知道是否需要驗證。
示例代碼 7:BasicReader.java
import java.io.*; import java.net.*;
public class BasicReader { public static void main(String argv[]) throws Exception { Authenticator.setDefault(new AuthImpl()); if (argv.length != 1) { System.err.println("Usage: java BasicReader <site>"); System.exit(1); } URL url = new URL(argv[[#333333]0[/#]]); URLConnection connection = url.openConnection(); BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line; StringBuffer sb = new StringBuffer(); while ((line = in.readLine()) != null) { sb.append(line); } in.close(); System.out.println(sb.toString()); System.exit(0); } } 運行下面的命令,用 Tomcat 來進(jìn)行測試:
prompt> java BasicReader http://localhost:8080/learn/chapter 如果你進(jìn)入的站點需要驗證 (它也這樣做了),那會像圖 7 那樣的對話框就會顯示出來。
 圖 7:Java 處理 HTTP 驗證
一旦你輸入了正確的用戶名和密碼,服務(wù)器就會允許你訪問。BasicReader 會讀取被請求頁面的 HTML 內(nèi)容并顯示在你的控制臺窗口。
特別注意:在 Tomcat 4.0 中使用摘要驗證時你可能會遇到問題,這是由 J2SE 1.4.0 和 J2SE 1.4.1 的一個 BUG 引起的。不過這個問題已經(jīng)在 J2SE 1.4.2 中解決了。詳情情看這里。
總結(jié)
這篇文章是一篇教程,它介紹了 java.net 包的高層 API。這些 API 使你可以快速簡捷地建立有用的網(wǎng)絡(luò)應(yīng)用程序,如 StockReader。這里也討論了 HTTP 驗證,并用實例演示了如何使用你自己的驗證方案。URL 和 URLConnection 還有一些優(yōu)勢沒有在文中提到,它們包括:自動重定向、自動管理保持的連接等。不過現(xiàn)在你已經(jīng)從文中獲得基礎(chǔ)知識,可以自己解決這些問題了。
|