Programmatic Authentication
The word programmatic here means implemented from scratch, which is a good choice for authentication if you must have portability or if you want total control. Because it's more work than relying on your servlet container, programmatic authentication can be a bad choice if you are not interested in those benefits.
Another drawback to programmatic authentication is that HttpServletRequest.getUserPrincipal, HttpServletRequest.getRemoteUser, and HttpServletRequest.isUserInRole are rendered useless for applications with programmatic authentication. Programmatic authentication requires you to implement, and use, your own API because setting principals and roles is strictly for servlet containers. See "Principals and Roles" on page 252 for more information about setting principals and roles.
The rest of this section discusses an authentication mechanism implemented from scratch; if you're interested in something similar, you can use it for ideas or perhaps as a starting point.
The authentication mechanism discussed in this section entails protecting JSP pages with a custom tag, like this:
<!--A protected JSP page--> ... <%@ taglib='/WEB-INF/tlds/security' prefix='security' %> ... <!-- errorPage is optional; if unspecified, control goes back to loginPage if login fails --> <security:enforceLogin loginPage='/login.jsp' errorPage='/error.jsp'/> <!--The rest of the file is accessed only if a user has logged into this session --> ...
The enforceLogin tag looks for a user in session scope. If the user is in the session, the tag does nothing; if not, the tag forwards to the login page. The login page is specified with the loginPage attribute.
If login fails, control is forwarded to the error page. The errorPage attribute is optional; without it, the login page is redisplayed if login fails.
When login succeeds, a user is created and placed in session scope, and the rest of the page after the enforceLogin tag is evaluated.
Figure 9-5 provides a more visual representation of the sequence of events initiated by the enforceLogin tag.
Figure 9-5 Enforce Login Tag Sequence Diagram
If no user is in session scope, three session attributes, listed in Table 9-9, are set by the enforceLogin tag.
Table 9-9 Session Attributes Set by the enforceLogin Tag
Attribute Name |
Description |
login-page |
The enforceLogin tag forwards to this page if there's no user in the session. If login subsequently fails and no error page is specified, control is returned to this page. |
error-page |
An optional error page that's displayed when login fails |
protected-page |
The page with the enforceLogin tag; when login succeeds, the rest of the page after that tag is evaluated. |
The attributes listed in Table 9-9 determine how the request is subsequently handled; the first two correspond to the loginPage and errorPage attributes of the enforceLogin tag, respectively. The protected-page attribute represents the URI of the protected page.
The login page submits the login form to a servlet. If that servlet authenticates the username and password, it redirects the request to the protected page; otherwise, it forwards to the error page, if specified, or back to the login page, if not.
Figure 9-6 shows an example that uses the programmatic authentication discussed in this section.
Figure 9-6 Programmatic Authentication
The top two pictures in Figure 9-6 show a failed login, and the bottom two show subsequent success. Figure 9-7 shows the files involved in the application shown in Figure 9-6.
Figure 9-7 Files for the Programmatic Authentication Example
The application maintains a makeshift database of users. That database is an instance of LoginDB and users are User instances; those classes are listed in Example 5-1.b on page 139 and Example 5-1.a on page 138, respectively. This implementation of LoginDB adds a default user, as listed in Example 9-5.a.
Example 9-5.a /WEB-INF/classes/beans/LoginDB.java
// The User class is listed in Example 5-1.a on page 138. ... public class LoginDB implements java.io.Serializable { private Vector users = new Vector(); private User[] defaultUsers = { new User("wtell", "william", "my first name"), }; public LoginDB() { for(int i=0; i < defaultUsers.length; ++i) users.add(defaultUsers[i]); } public void addUser(String uname, String pwd, String hint) { users.add(new User(uname, pwd, hint)); } // The rest of this class is identical to LoginDB listed in // Example 5-1.b on page 139. ... }
The application shown in Figure 9-6 has one protected page, listed in Example 9-5.b.
Example 9-5.b /protectedPage.jsp
<html><head><title>A Protected Page</title></head> <%@ taglib uri='security' prefix='security' %> </body> <!-- Without the errorPage attribute, control is forwarded back to the login page if login fails. --> <security:enforceLogin loginPage='/login.jsp' errorPage='/error.jsp'/> <jsp:useBean id='user' type='beans.User' scope='session'/> This is a protected page. Welcome <%= user.getUserName() %>. </body> </html>
The protected page accesses the user in the session to display a welcome message. The enforceLogin tag handler is listed in Example 9-5.c.
Example 9-5.c /WEB-INF/classes/tags/EnforceLoginTag.java
package tags; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.TagSupport; public class EnforceLoginTag extends TagSupport { private String loginPage, errorPage; public void setLoginPage(String loginPage) { this.loginPage = loginPage; } public void setErrorPage(String errorPage) { this.errorPage = errorPage; } public int doEndTag() throws JspException { HttpSession session = pageContext.getSession(); HttpServletRequest req = (HttpServletRequest)pageContext. getRequest(); String protectedPage = req.getRequestURI(); if(session.getAttribute("user") == null) { session.setAttribute("login-page", loginPage); session.setAttribute("error-page", errorPage); session.setAttribute("protected-page", protectedPage); try { pageContext.forward(loginPage); return SKIP_PAGE; } catch(Exception ex) { throw new JspException(ex.getMessage()); } } return EVAL_PAGE; } public void release() { loginPage = errorPage = null; } }
If there's a user in the session, the tag handler listed in Example 9-5.c returns EVAL_PAGE and the rest of the page after the tag is evaluated. If the user is not in the session, the attributes listed in Table 9-9 on page 273 are set and control is forwarded to the login page.
The login page is listed in Example 9-5.d.
Example 9-5.d /login.jsp
<html><head><title>Login Page</title></head> <%@ taglib uri='/WEB-INF/tlds/security.tld' prefix='security' %> <body> <font size='4' color='red'><security:showErrors/></font> <p><font size='5' color='blue'>Please Login</font><hr> <form action='<%= response.encodeURL("authenticate") %>' method='post'> <table> <tr> <td>Name:</td> <td><input type='text' name='userName'/> </td> </tr><tr> <td>Password:</td> <td><input type='password' name='password' size='8'></td> </tr> </table> <br> <input type='submit' value='login'> </form></p> Note: valid name is <i>wtell</i> and valid password is <i>william</i> </body> </html>
The login form is submitted to the authenticate servlet, which generates error messages in session scope if authentication fails. Those messages are displayed by the security:showErrors tag at the top of the login page. The mappings between the name authenticate and the authenticate servlet are specified in web.xml, which is listed in Example 9-5.e.
Example 9-5.e /WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd"> <web-app> <servlet> <servlet-name>authenticate</servlet-name> <servlet-class>AuthenticateServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>authenticate</servlet-name> <url-pattern>/authenticate</url-pattern> </servlet-mapping> <taglib> <taglib-uri>/WEB-INF/tlds/security.tld</taglib-uri> <taglib-location>/WEB-INF/tlds/security.tld</taglib-location> </taglib> </web-app>
Example 9-5.f lists the authenticate servlet.
Example 9-5.f /WEB-INF/classes/AuthenticateServlet.java
import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; import beans.LoginDB; import beans.User; public class AuthenticateServlet extends HttpServlet { private LoginDB loginDB; public void init(ServletConfig config) throws ServletException{ super.init(config); loginDB = new LoginDB(); } public void service(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { HttpSession session = req.getSession(); String uname = req.getParameter("userName"); String pwd = req.getParameter("password"); User user = loginDB.getUser(uname, pwd); if(user != null) { // authorized String protectedPage = (String)session. getAttribute("protected-page"); session.removeAttribute("login-page"); session.removeAttribute("error-page"); session.removeAttribute("protected-page"); session.removeAttribute("login-error"); session.setAttribute("user", user); res.sendRedirect(res.encodeURL(protectedPage)); } else { // not authorized String loginPage = (String)session. getAttribute("login-page"); String errorPage = (String)session. getAttribute("error-page"); String forwardTo = errorPage != null ? errorPage : loginPage; session.setAttribute("login-error", "Username and Password are not valid."); getServletContext().getRequestDispatcher( res.encodeURL(forwardTo)).forward(req,res); } } }
The authenticate servlet obtains the username and password from the request and attempts to obtain a reference to a corresponding user in the login database. If the user exists in the database, session attributes generated by the servlet and the enforceLogin tag are removed from the session and the request is redirected to the protected page. Figure 9-8 shows the sequence of events for a successful login.
Figure 9-8 Login Succeeds Sequence Diagram
If the user is not in the login database, a login-error session attribute is set and the request is forwarded to the error page, if specified, or back to the login page, if not. Figure 9-9 shows the sequence of events for a failed login.
Figure 9-9 Login Fails Sequence Diagram
The error page for the application in Figure 9-6 on page 273 is listed in Example 9-5.g.
Example 9-5.g /error.jsp
<html><head><title>Login Error</title></head> <%@ taglib uri='/WEB-INF/tlds/security.tld' prefix='security' %> <body> <font size='4' color='red'> Login failed because:<p> <security:showErrors/></font></p> Click <a href='login.jsp'>here</a> to retry login. </body> </html>
Like the login page, the error page uses the security:showErrors tag, whose handler is listed in Example 9-5.h.
Example 9-5.h /WEB-INF/classes/tags/ShowErrorsTag.java
package tags; import javax.servlet.jsp.JspException; import javax.servlet.jsp.PageContext; import javax.servlet.jsp.tagext.TagSupport; public class ShowErrorsTag extends TagSupport { public int doStartTag() throws JspException { String error = (String)pageContext.getSession(). getAttribute("login-error"); if(error != null) { try { pageContext.getOut().print(error); } catch(java.io.IOException ex) { throw new JspException(ex.getMessage()); } } return SKIP_BODY; } }
The showErrors tag handler prints the value of the login-error session attribute that was set by the authenticate servlet.