跨会话识别用户
要在用户关闭和重新打开浏览器之后恢复 Web 表单,应用程序要能跨会话识别用户,如果用户做过身份认证,这一点并不难实现。如果应用程序使用标准方法进行用户认证,就可以调用 getUserPrincipal() 方法,此方法在 HttpServletRequest 接口中定义,然后再用 java.security.Principal 的 getName() 方法获取用户名。
如何应用程序支持匿名用户的表单保存和表单恢复特性,您也可以设置浏览器 ID,这个 ID 与会话 ID 十分类似,只不过后者是在单一的会话中跟踪用户。实际上,当用户首次访问应用程序时,可以取得会话 ID cookie 的值并设置另一个名为 BROWSERID 的 cookie。与会话开始生成、会话结束失效的会话 ID 不同,BROWSERID cookie 只设置一次,且可以在很长一段时间(比如几年)后才失效。
使用 servlet 过滤器
servlet 过滤器非常适合设置 BROWSERID cookie,因为当用户首次访问应用程序时,此过滤器可以截取每个 HTTP 请求,并将此 cookie 添加到 HTTP 响应。一旦浏览器通过第一个响应收到此 cookie,那么所有后续响应都将包含 BROWSERID cookie,这样应用程序能够通过其浏览器 ID 来识别匿名用户。本文所附带的示例代码包含一个名为 BrowserIdFilter 的类,它实现了 javax.servlet.Filter。此类还有一个名为 getBrowserId() 的方法(参见 清单 1),它迭代请求对象的 cookie,返回 BROWSERID cookie 的值或 null(如果这样的 cookie 不存在)。
清单 1. 获得浏览器 ID cookie
...
import javax.servlet.http.Cookie;
...
public class BrowserIdFilter implements Filter {
public static String BROWSERID = "BROWSERID"; // cookie name
public static String getBrowserId(HttpServletRequest httpRequest) {
String browserId = null;
Cookie cookies[] = httpRequest.getCookies();
if (cookies != null)
for (int i = 0; i < cookies.length; i++) {
if (BROWSERID.equals(cookies[i].getName())) {
browserId = cookies[i].getValue();
break;
}
}
return browserId;
}
...
}
清单 2 给出了 BrowserIdFilter 的 doFilter() 方法,此方法使用 getBrowserId() 测试 cookie 是否尚未设置。如果 getBrowserId() 返回 null,doFilter() 就会用 getSession() 获得会话对象,用 getId() 获得会话 ID。之后,doFilter() 会创建 Cookie 对象,设置 maxAge 和 path 属性,并会将此 cookie 添加到响应对象。为确保对 HTTP 请求的合适处理,request 和 response 对象会被传递给 chain 参数的 doFilter() 方法。
清单 2. 实现过滤器接口的 doFilter() 方法
...
import javax.servlet.Filter;
import javax.servlet.FilterChain;
...
public class BrowserIdFilter implements Filter {
public static String BROWSERID = "BROWSERID"; // cookie name
public static int IDAGE = 3600 * 24 * 365 * 3; // three years
...
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (getBrowserId(httpRequest) == null) {
// The BROWSERID cookie has not been found. This must be
// the first time the user accesses the application.
// Use the current session's ID as the value for
// the BROWSERID cookie.
HttpServletResponse httpResponse
= (HttpServletResponse) response;
String browserId = httpRequest.getSession().getId();
Cookie browserCookie = new Cookie(BROWSERID, browserId);
browserCookie.setMaxAge(IDAGE);
browserCookie.setPath(httpRequest.getContextPath());
httpResponse.addCookie(browserCookie);
}
chain.doFilter(request, response);
}
...
}
配置 servlet 过滤器
清单 3 展示了如何在 web.xml 中配置 BrowserIdFilter 类,以便过滤器能够截获每个 HTTP 请求。
清单 3. 配置过滤器
...
<filter>
<filter-name>BrowserIdFilter</filter-name>
<filter-class>autosave.BrowserIdFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>BrowserIdFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
浏览器 ID 的解决方案只能用在表单不 包含任何敏感信息的情况下,原因是所有通过这个共享的计算机使用此应用程序的用户都会被视为同一个用户。 跨会话识别用户的惟一一种最安全的方案是使用基于用户名和密码的标准身份认证方法。但基于密码的身份认证要求用户必须登录到应用程序,这很不方便。在很多时候,安全性最为重要,但有时,与其注册到站点,用户更愿意保持匿名。浏览器 ID 提供了识别匿名用户的一种简便方式。
本系列所给出的表单自动保存特性适用于认证的和匿名的这两类用户。在接下来的章节中,您将看到如何存储和检索自动保存的表单数据以及如何在多线程的环境中使用表单数据。