In my previous article, "Solving the Logout Problem Properly and Elegantly" (JavaWorld, September 2004), I addressed the logout functionality in a Web application, specifically the problem created by a browser's Back button. The solution I proposed indeed prevents users from accessing any restricted page via the Back button once they complete the logout process. However, if logged out users continue clicking the Back button, they will eventually come back to the resource that was the action of the login-form POST request. And at this point, the browser begins displaying confusing warning messages.
The confusing warning messages appear in IE browsers as follows:
Warning: Page has Expired. The page you requested was created using information you submitted in a form. This page is no longer available. As a security precaution, Internet Explorer does not automatically resubmit your information for you
To resubmit your information and view this Webpage, click the Refresh button.
And in Firefox browsers:
The page you are trying to view contains POSTDATA that has expired from cache. If you resend the data, any action from the form carried out (such as a search or online purchase) will be repeated. To resend the data, click OK. Otherwise, click Cancel.
Now, without resupplying the user ID and password, by just simply clicking on the OK button, users (or hackers) can access restricted resources again, which defeats the purpose of the logout process. In my previous article, we tracked the last login time to address this security hole. This solution gets the job done, but is not ultimately perfect.
This article revisits the problems created by the browser's Back button and eliminates the remaining flaw by preventing such warning messages from ever appearing. The previous solution targeted JSP (JavaServer Pages) and Struts. In this article, I present a solution for both JavaServer Faces and Stripes, two popular Java-based Web frameworks. By the end of this article, readers will have two recipe-style JSF and Stripes solutions that many Web applications, such as Microsoft Office Project Web Access, still lack.
Note: I assume readers are already familiar with both JSF and Stripes, as this article is not a beginner's tutorial on how to use those frameworks.
The problem with JSP/servlet forward
The reason why the browser throws the confusing warning messages and allows a security hole must first be explained. The controller that is the action of a login-form POST "naively" dispatches a forward to a second JSP page upon authenticating the credential. Figure 1 depicts the control flow.
The problem stems from the fact that a JSP/servlet forward dispatch is entirely internal to the container, and the browser is only aware of the original action resource, namely loginAction.jsp. After the login form submission (with a valid user ID/password) and the forward dispatch, the dynamic HTML content of securePage.jsp is served to the browser. With each subsequent browser's reload/refresh, the browser reloads loginAction.jsp, not securePage.jsp, since it only knows the former and the latter is completely transparent to it. Since loginAction.jsp is the action of a POST request that contains input data, the browser displays the warning messages for confirmation purposes. The browsers behave similarly when reloading the resource loginAction.jsp after users click the Back button. With the browsers remembering the POST input data, and with users (or malicious hackers) simply clicking the warning message's OK button, users (or hackers) can access restricted resources again, which defeats the purpose of the logout process. I came up with a solution that tracks last login time, which I described in my previous article, but it needs improvement.
Another problem, although minor, that has not been discussed is that while the browser renders the HTML code from the view securePage.jsp, the browser address bar shows loginAction.jsp. A majority of users will probably never pay attention to this detail and, even if they do, they probably won't care, but to the meticulous ones, something just appears out of sync.
The solution is surprisingly simple, as Figure 2 illustrates.
The controller loginAction.jsp finishes the authentication logic and performs a redirect dispatch instead of a forward dispatch to securePage.jsp. Essentially, the redirect involves an extra round-trip, where the Web application instructs the browsers to fetch the page securePage.jsp. The browser is then aware of both the original resource (login.jsp) and the second resource (securePage.jsp). As a result, the browser's history stack only contains login.jsp followed by securePage.jsp, while loginAction.jsp is transparent to the browser. This is exactly the preferred situation.
Both reloading/refreshing and the clicking of the Back button will never lead the browser to load loginAction.jsp, which is associated with a POST request. Since no POST request is repeated, there are no issues with POST input data, and the browser will not throw warning messages to users. A by-product of this solution is that it can be applied to solve problems due to double submission (a subject that reaches beyond this article's scope).
This section presents the JSF recipe for our proposed solution in which the key is to perform a redirect right after the login form POST. Figure 3 depicts a page flow in a JSF application that handles login and logout functionality. Rectangle boxes represent JSF managed beans, and rounded rectangle boxes represent JSP pages. The caption in rectangle boxes is in the format of
class.method, and the caption in rounded boxes denotes, not surprisingly, the JSP file name.
The steps in the JSF recipe are now explained: