将数据提交到网页服务器
在上面的例子中,数据是作为 URL 的一部分被送到服务器的,使用的 GET 方法。现在来看一个使用 POST方法发送数据的例子。这个例子中,http://www.javacourses.com/cgi-bin 中的 CGI 脚本 (名为 .cgi)需要 name 和 email 值。如果用户提交 Sally McDonald 作为 name 值,smc@yahoo.com 作为email 值,CGI 脚本会获取输入并对消息进行解析、解码,再将提交的内容返回给客户端。这个 CGI脚本做事情并不多,但我们要用它来演示如何向服务器提交数据。
还有一点非常重要——请注意使用 POST 方法时,消息内容的类型是 application/x-www-form-urlencoded,这种类型会:
- 指定常规数据编码
- 将空格转换为加号 (+)
- 将非文本内容转换成十六进制数后接百分号 (%) 的形式
- 在每个 name=value 对之间放置 & 符号
根据这个编码规则,消息 (name=Sally McDonald and email=smc@yahoo.com) 在发送给 CGI 脚本之前必须被编码成:
name=Sally+McDonald&email=smc@yahoo.com
CGI 脚本收到这个已编码的消息后会对它进行解码。不过非常幸运,你不必为手工进行编码操作。你可以使用java.net.URLEncoder 类来对消息进行编码,就像下面的例子中那样。相应地你可以使用 java.net.URLDecoder来对消息进行解码。
示例代码 5 中是这个例子的实现 (使用 HttpURLConnection 类),它展示了如何使用 POST 方法向服务器发送数据。你会看到:
- 为 CGI 脚本打开连接和 I/O 流
- 设置请求方法为 POST
- 使用 URLEncoder.encode 方法对消息进行编码 (URLDecoder.decode 方法可以用于解码)
- 向 CGI 脚本发送已经编码的消息
- 接收服务器返回的消息并在控制台打印出来
示例代码 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.javacourses.com/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@javacourses.com " , "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 ();
}
}
代理服务器和防火墙
如果你使用了防火墙,你就得把代理服务器以及端口号的详细信息告诉 Java,这样才能访问到防火墙外的主机。你可以通过定义一些 HTTP 或者 FTP 属性来做到:
http.proxyHost (default: )
http.proxyPort (default: 80 if http.proxyHost specified)
http.nonProxyHosts (default: )
http.proxyHost 和 http.proxyPort 用来指定 HTTP协议处理器需要使用的代理服务器和端口号。http.nonProxyHosts 用来指定哪些主机是直接连接的(即不通过代理服务器来连接)。http.nonProxyHosts 属性的值是一个由 |分隔开的主机列表,它可以使用正则表达式来表示所匹配的主机,如:*.sfbay.sun.com 将匹配 sfbay 域中的任何主机。
ftp.proxyHost (default: )
ftp.proxyPort (default: 80 if ftp.proxyHost specified)
ftp.nonProxyHosts (default: )
ftp.proxyHost 和 ftp.proxyPort 用来指定 FTP 协议处理器需要使用的代理服务器和端口号。ftp.nonProxyHosts 用来指定哪些主机是直接联系的,指定的方法与 http.nonProxyHosts 类似。
你可以在应用程序启动的时候设置这些属性:
Prompt> java -Dhttp.proxyHost=HostName -Dhttp.proxyPort=PortNumber yourApp
HTTP 验证
HTTP 协议提供验证机制来保护资源。当一个请求要求取得受保护的资源时,网页服务器回应一个 401 Unauthorized error错误码。这个回应包含一个指定了验证方法和领域的 WWW-Authenticate头信息。把这个领域想像成一个存储着用户名和密码的数据库,它将被用来标识受保护资源的有效的用户。比如,你试着去访问 某个网站上标识为“Personal Files”的资源,服务器响应可能是:WWW-Authenticate: Basic realm="PersonalFiles" (假设验证方法是 Basic)。
验证方法
现在有几种用于网络应用的验证方法,其中最广泛使用的是基本验证 (Basic Authentication) 和摘要验证 (Digest Authentication)。
当用户想要访问有限的资源时,使用基本验证方法的网页服务器会要求浏览器询显示一个对话框,并要求用户输入用户名和密码。如果用户输入的用户名和密码正 确,服务器就允许他访问这些资源;否则,在接连三次尝试失败之后,会显示一个错误消息页面。这个方法的缺点是用户名和密码都是用 Base64 编码(全是可读文本) 之后传输过去的。也就是说,这个验证方法的安全程度只是和 Telnet 一样,并不是非常安全。
数据验证方 法不会在网络中传输密码,而是生成一些数字 (根据密码和其它一些需要的数据产生的) 来代替密码,而这些数字是经过 MD5(Message Digest Algorithm)加密的。生成的值在网络有随着服务器需要用来校难密码的其它信息一起传输。这个方法明显更为安全。
基于表单的验证方法和基本验证方法类似,只是服务器使用你自定义的登录页面来代替了标准的登录对话框。
最后,客户证书验证使用 SLL (Secure Socket Layer,安全套接层) 和客户证明。
在 Tomcat 下保护资源
你可以在 tomcat-users.xml 文件中写一个用户及其角色的列表。这个文件在 TOMCAT_HOME (你安装 Tomcat的目录) 下的 conf 目录中。这个文件默认包含了三个用户 (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 分别设置为 author 和 reader。角色属性非常重要,因为当你创建安全规则的时候,每个受限制的资源都是与可访问它的角色相关联 (稍后你会看到)。
下面做个实验 (假设你已经安装并配置好了 Tomcat)。为你期望的页应用程序建立一个目录。可以按下列步骤做好准备:
- 在你安装了 Tomcat 的目录下,有一个目录是 webapps。在这个目录下建立一个目录 (如:learn)。
- 在第一步建立的目录下建立一个子目录,命名为 chapter。
- 在 chapter 目录中,建立一个 HTML 文件,内容自定,文件名为 index.html。
- 在第一步建立的目录下建立一个名为 WEB-INF 的子目录。
- 在 WEB-INF 目录中创建一个名为 web.xml 的文件,该文件内容如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/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 是描述配置的文件,这里集中说明一下安全相关的配置元素。
- <security-constraint>:这个元素限制对一个或者多个资源的访问,可以在配置信息中出现多次。上面的配置信息中,它限制了 chapter 目录 (http://localhost:8080/learn/chapter )下所有资源的访问。<security-constraint> 包含了下列元素:
- <web- resource-collection>:这个元素用于标识你想限制访问的资源。你可以定义 URL 模式 和 HTTP 方法 (用<http-method> 元素定义 HTTP 方法)。如果没有定义 HTTP方法,那么限制将应用于所有方法。在上面的应用中,我想限制访问的资源是http://localhost:8080/learn/chapter/ *,也就是 chapter 目录下的所有文档。
- <auth-constraint>:这个元素可以访问上面定义的受限资源的用户角色。在上面的应用中,tomcat、author 和 erader 这三个角色可以访问这些资源。
- <login-config>:这个元素用于指定验证方法。它包含下列元素:
- <auth-method>:指定验证方法。它的值可能是下列值集中的一个:BASIC (基本验证)、DIGEST (摘要验证)、FORM (基于表单的验证) 或者 CLIENT-CERT (客户证书验证)。
- <realm-name>:如果选用 BASIC 方法进行验证的时候,标准登录对话框中的一个描述名称。
示例
上述配置中使用了 BASIC 验证方法。下面我们做个实验:启动你的 Tomcat 服务器并向它发送到 http://localhost:8080/learn/chapter 的请求。这时候,就会有像图 3 所示那样的对话框提示你输入用户和密码:
图 3:HTTP 基本验证 (Basic Authentication)
输入一个用户及其密码 (你可以看看 tomcat-users.xml 文件),这个用户的角色应该在配置 (web.xml) 中存在。如果你输入的用户名和密码正确,你就能访问到那些资源;否则,你还可以再试两次。
使用摘要验证来实验:
- 关闭你的 Tomcat 服务器。
- 修改你的配置文件 (web.xml),把 BASIC 换成 DIGEST。
- 重新启动你的 Tomcat 服务器。
- 打开一个新的浏览器窗口。
- 在地址栏中输入 http://localhost:8080/learn/chapter 并回车。
你会看到类似的对话框。从图 4 你可以看到,这个登录对话框是安全的,因为使用了摘要验证。
图 4:HTTP 摘要验证 (Digest Authentication)
服务器在幕后的回复
当使用基本验证方法保护资源的时候,服务器发回类似于图 5 所示的响应信息:
图 5:服务器回复 (基本验证)
如果是使用的摘要验证方法来保护的资源,服务器发回的响应信息就像图 6 所示的那样:
图 6:服务器回复 (摘要验证)
Java 支持 HTTP 验证
J2SE (1.2 或者更高版本) 通过 Authenticator 类为验证提供了本地支持。你所要做的只是继承这个类并实现它的getPasswordAuthentication 方法。这个方法取得用户名和密码并用它们生成一个PasswordAuthentication 对象返回。完成之后,你还得使用 Authenticator.setDefault 方法注册你的Authenticator 实例。现在,只要你想访问受保护的资源,就会调用getPasswordAuthentication。Authenticator 类管理着所有低层的详细资料。它不受 HTTP的限制,可以应用于所有网络连接,这不能不说是一个好消息。
示例代码 6 中是实现 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 实例开始运行。我不必去解析任何服务器返回的信息以检查是否需要验证,因为 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 来进行测试:
prompt> java BasicReader http://localhost:8080/learn/chapter
如果你进入的站点需要验证 (它也这样做了),那会像图 7 那样的对话框就会显示出来。
图 7:Java 处理 HTTP 验证
一旦你输入了正确的用户名和密码,服务器就会允许你访问。BasicReader 会读取被请求页面的 HTML 内容并显示在你的控制台窗口。
特别注意 :在 Tomcat 4.0 中使用摘要验证时你可能会遇到问题,这是由 J2SE 1.4.0 和 J2SE 1.4.1 的一个 BUG 引起的。不过这个问题已经在 J2SE 1.4.2 中解决了。详情情看这里。
总结
这篇文章是一篇教程,它介绍了 java.net 包的高层 API。这些 API 使你可以快速简捷地建立有用的网络应用程序,如StockReader。这里也讨论了 HTTP 验证,并用实例演示了如何使用你自己的验证方案。URL 和 URLConnection还有一些优势没有在文中提到,它们包括:自动重定向、自动管理保持的连接等。不过现在你已经从文中获得基础知识,可以自己解决 这些问题了。