5.2.10 客户端身份验证
有时需要对某些网络资源(如Servlet、JSP等)进行访问权限验证,也就是说,有访问权限的用户才能访问该网络资源。进行访问权限验证的方法很多,但通过HTTP响应消息头的WWW-Authenticate字段进行访问权限的验证应该是众多权限验证方法中比较简单的一个。
通过HTTP响应消息头的WWW-Authenticate字段可以使浏览器出现一个验证对话框,访问者需要在这个对话框中输入用户名和密码,然后经过服务端验证,才可以正常访问网络资源的内容。但要注意,在发送WWW-Authenticate字段的同时,还要使用HttpServletResponse接口的setStatus方法将响应码设为401(HttpServletResponse.SC_UNAUTHORIZED),否则不会出现验证对话框。
通过WWW-Authenticate字段进行验证有两种方式:BASIC和DIGEST。其中BASIC方式比较简单,密码通过Base64编码格式进行传输,实际上就是通过明文进行传输。而DIGEST可以对传输的用户名、密码等敏感信息进行加密,也更加安全,但是实现起来也更复杂。在本节中将使用BASIC方式进行验证,关于DIGEST的详细信息,感兴趣的读者可以参考RFC2617(http://www.ietf.org/rfc/rfc2617.txt)。
进行验证的基本过程是首先判断HTTP请求消息头是否有Authorization字段,如果有这个字段,说明用户曾经登录过,可能登录成功,也可能登录失败,只要是输入了用户名和密码,并单击【确定】按钮后,再次在同一个浏览器窗口访问该Servlet,浏览器就会在HTTP请求消息头中加入Authorization字段,格式如下:
Authorization:Basic YWRtaW46MTIzNDEx
其中Basic是验证的类型,后面是被Basic64格式的用户名和密码信息。
如果HTTP请求消息头中没有Authorization字段,或者不是Basic验证,则在Servlet中要设置WWW-Authenticate响应消息头字段,格式如下:
WWW-Authenticate:BASIC realm="/demo”
其中realm表示当前资源所属的域,可以是任意字符串,一般情况下,同一个Web应用程序要将这个属性值设成同一个值,如可以设成上下文路径(通过getContentPath方法获得)。
在下面将给出一个实际的例子来演示如何使用BASIC方式进行身份验证。
例子 : 客户端身份验证
1. 实例说明
在每一次访问本例中的程序(Servlet)时,将会弹出一个权限验证对话框,要求输入“用户名”和“密码”。用户名和密码输入正确后,就会进入相应的页面。当再次访问当前页面时,就不会弹出权限验证对话框了,而是直接进入当前访问的页面。如果在浏览器的新窗口再次访问该Servlet时,仍然会弹出权限验证对话框,并重复上述的权限验证过程。
2. 编写AuthenticateServlet类
AuthenticateServlet类负责效验用户输入的用户名和密码,并且当第一次访问Servlet时设置WWW-Authenticate响应消息头字段,以通知浏览器显示权限验证对话框。AuthenticateServlet类的实现代码如下:
package chapter5;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class AuthenticateServlet extends HttpServlet
{
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
// 得到Authorization字段的值
String base64Auth = request.getHeader("Authorization");
if (base64Auth == null || !base64Auth.toUpperCase().startsWith("BASIC"))
{
// 通知浏览器弹出验证对话框
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setHeader("WWW-Authenticate", "BASIC realm=\""+ request.getContextPath() + "\"");
out.println("需要进行身份验证!");
return;
}
sun.misc.BASE64Decoder base64Decoder = new sun.misc.BASE64Decoder();
String auth = new String(base64Decoder.decodeBuffer(base64Auth.substring(6)));
String[] array = auth.split(":"); // 将用户名和密码分开
if(array.length == 2)
{
String user = array[0].trim();
String password = array[1].trim();
RequestDispatcher rd = request.getRequestDispatcher("HeaderInfo");
rd.include(request, response); // 输出所有的HTTP请求头
// 进行身份验证
if(user.equals("admin") && password.equals("1234"))
out.println("身份验证成功,该Servlet已经进入!");
else
out.println("身份验证失败!");
}
}
}
从上面的代码可以看出,AuthenticateServlet类首先判断了请求消息头字段Authorization是否存在,如果该字段不存在,则设置了响应消息头字段WWW-Authenticate和状态码401,以通知浏览器显示权限验证对话框。如果Authorization字段存在,并且为Basic验证,则从Authorization字段值中取出用户名和密码进行验证,并输出验证结果信息。
3. 配置AuthenticateServlet类
AuthenticateServlet类的配置代码如下:
<servlet>
<servlet-name>AuthenticateServlet</servlet-name>
<servlet-class>chapter5.AuthenticateServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>AuthenticateServlet</servlet-name>
<url-pattern>/AuthenticateServlet</url-pattern>
</servlet-mapping>
4. 测试
在浏览器地址栏中输入如下的URL:
http://localhost:8080/demo/AuthenticateServlet
浏览器会弹出一个权限验证对话框,并在【用户名】和【密码】文本框中分别输入“admin”和“1234”,如图5.14所示。
图5.14 权限验证对话框
单击【确定】按钮,浏览器将显示如图5.15所示的信息。
图5.15 权限验证成功后显示当前访问页面的内容
5. 程序总结
要注意的是,在单击图5.14所示的【确定】按钮后,浏览器会再次访问AuthenticateServlet,这时HTTP请求消息头已经包含了authorization字段,如图5.15的黑框中所示。如果单击【取消】按钮,浏览器不会再次访问AuthenticateServlet,同时,AuthenticateServlet会在浏览中输出“需要进行身份验证!”信息。
在等一次登录成功后,在同一个浏览器窗口再次访问AuthenticateServlet,在HTTP请求消息头中就会包含authorization字段,因此,也就不再需要进行身份验证了。