|
Recipe 11.5. Implementing "Remember Me" LoginsProblemYou want to provide a "remember me" feature so a user's username and password are prefilled on the logon form if that user has logged on before. SolutionIn your Action that logs a user in, create persistent cookies containing the user's base-64 encoded username and password. The private saveCookies( ) and removeCookies( ) methods shown in Example 11-8 manipulate the cookies as needed. Example 11-8. An Action that stores or removes cookiespackage com.oreilly.strutsckbk.ch11; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.commons.beanutils.PropertyUtils; import org.apache.struts.action.Action; import org.apache.struts.action.ActionErrors; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import com.oreilly.servlet.Base64Encoder; public final class MyLogonAction extends Action { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession( ); ActionErrors errors = new ActionErrors( ); String username = (String) PropertyUtils.getSimpleProperty(form, "username"); String password = (String) PropertyUtils.getSimpleProperty(form, "password"); boolean rememberMe = ((Boolean) PropertyUtils.getSimpleProperty( form, "rememberMe")).booleanValue( ); // Call your security service here //SecurityService.authenticate(username, password); if (rememberMe) { saveCookies(response, username, password); } else { removeCookies(response); } session.setAttribute("username", username); return mapping.findForward("success"); } private void saveCookies(HttpServletResponse response, String username, String password) { Cookie usernameCookie = new Cookie("StrutsCookbookUsername", Base64Encoder.encode(username)); usernameCookie.setMaxAge(60 * 60 * 24 * 30); // 30 day expiration response.addCookie(usernameCookie); Cookie passwordCookie = new Cookie("StrutsCookbookPassword", Base64Encoder.encode(password)); passwordCookie.setMaxAge(60 * 60 * 24 * 30); // 30 day expiration response.addCookie(passwordCookie); } private void removeCookies(HttpServletResponse response) { // expire the username cookie by setting maxAge to 0 // (actual cookie value is irrelevant) Cookie unameCookie = new Cookie("StrutsCookbookUsername", "expired"); unameCookie.setMaxAge(0); response.addCookie(unameCookie); // expire the password cookie by setting maxAge to 0 // (actual cookie value is irrelevant) Cookie pwdCookie = new Cookie("StrutsCookbookPassword", "expired"); pwdCookie.setMaxAge(0); response.addCookie(pwdCookie); } } When a user goes to the logon page, fill the username and password fields with values from the cookie, decoded from base-64, using the Struts bean:cookie tag, as shown in Example 11-9. Example 11-9. Setting logon form field values from cookies<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="com.oreilly.servlet.*" %> <%@ taglib uri="/tags/struts-bean" prefix="bean" %> <%@ taglib uri="/tags/struts-html" prefix="html" %> <html> <head> <title>Struts Cookbook - Cookie Logon</title> </head> <body> <html:errors/> <html:form action="/SubmitCookieLogon" focus="username"> <bean:cookie name="StrutsCookbookUsername" value=""/> <bean:cookie name="StrutsCookbookPassword" value=""/> <table border="0" width="100%"> <tr> <th align="right"> <bean:message key="prompt.username"/>: </th> <td align="left"> <html:text property="username" size="16" maxlength="18" value="<%=Base64Decoder.decode(uname.getValue( ))%>"/> </td> </tr> <tr> <th align="right"> <bean:message key="prompt.password" bundle="alternate"/>: </th> <td align="left"> <html:password property="password" size="16" maxlength="18" redisplay="false" </td> </tr> <tr> <th align="right"> <bean:message key="prompt.rememberMe"/>: </th> <td align="left"> <html:checkbox property="rememberMe"/> </td> </tr> <tr> <td align="right"> <html:submit property="Submit" value="Submit"/> </td> <td align="left"> <html:reset/> </td> </tr> </table> </html:form> </body> </html> DiscussionA cookie consists of a name-value data pair that can be sent to a client's browser and then read back again at a later time. Browsers provide security for cookies so a cookie can only be read by the server that originally created it. Cookies must have an expiration period.
The logon Action of Example 11-8 retrieves the username and password from the logon form. This form includes the true/false property rememberme, which indicates if the users want their login credentials remembered. If users want to be remembered, they check the checkbox for the rememberme property. In the MyLogonActionif rememberme is truethe cookies are created and saved in the response. If rememberme is false, the cookies for username and password have their maxAge set to 0, effectively removing them from the response. The bean:cookie tags used in Example 11-9 retrieve the cookie values from the request and store them in scripting variables. These tags specify the empty string ("") as the default value in case cookies are disabled. The initial values for the login form fields are set to the values from the scripting variables. The Solution shown here does not address cookie security issues. For a production system, the data sent in the cookies should be encrypted. A simple encryption scheme, such as MD5 or a variant of the Secure Hash Algorithm (SHA), can be used to encrypt the cookie value when it is created. Since the server creates the cookie and is the only party that can legitimately use the data, it can encrypt and decrypt the data using the algorithm of its own choosing. Alternatively, you can send the cookies only over HTTPS, thereby providing encryption/decryption at the transport level. See AlsoYou can use cookies to log in a user automatically; in other words, if users have a cookie(s) with valid credentials for the web application they don't have to submit the login form at all. The automatic login approach is shown in Recipe 11.7. Recipe 11.10 shows how to use the open source SecurityFilter software to implement "remember me" functionality. Its implementation includes support for cookie encryption and other settings. Java Servlet Programming by Jason Hunter (O'Reilly) covers servlet development from top to bottom, including the Cookie APIs. The Base64 encoder and decoder used in this recipe are part of the companion com.oreilly.servlet classes available from http://www.servlets.com/cos/. The foundation of Java server-side cookie handling is the Servlet Specification and API, available for download from http://java.sun.com/products/servlet/download.html. The JavaBoutique has a nice tutorial on server-side cookie handling found at http://javaboutique.internet.com/tutorials/JSP/part09/. Alexander Prohorenko has written a good article on cookie security issues for O'Reilly's ONLamp.com site at http://www.onlamp.com/pub/a/security/2004/04/01/cookie_vulnerabilities.html. |
|