Friday, April 13, 2007

Simple Authentication Servlet Filter with JSP/Servlet

Servlet filters in Java allow you centralized management of tasks such as authetication, compression, auding and logging, image conversion, and localization of web content, among others. The servlet filter I discuss here only demonstrates how a simple Java-based web application could add a form of authentication. Doing this saves you from having to check whether users are logged in, page is expired, or other permissions on every page, freeing each web page to focus more on its content.

The design scenario is simple: when an HTTP request arrives, users are sent to a login page (e.g. "login.jsp") if they are not logged in. Otherwise, authenticated users are sent to whatever other page they requested. When users log in, a servlet (call it "/auth") handles the authentication and sends the login page again if login fails, or sends the user to a landing page (e.g. "index.jsp") if successful.

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
System.out.print("Authentication: Request received ...");
try {
boolean authorized = false;
if (request instanceof HttpServletRequest) {
HttpSession session = ((HttpServletRequest)request).getSession(false);
if (session != null) {
UserToken token = (UserToken) session.getAttribute("userToken");
String qryStr = ((HttpServletRequest)request).getQueryString();
if((token != null) || "cLogin".equalsIgnoreCase(qryStr))
authorized = true;
}
}

if (authorized) {
chain.doFilter(request, response);
} else if (filterConfig != null) {
ServletContext context = filterConfig.getServletContext();
String login_page = context.getInitParameter("login_page");
System.out.print("Authentication: Login page = " + login_page);
if (login_page != null && !"".equals(login_page)) {
context.getRequestDispatcher(login_page).forward(request, response);
}
} else {
throw new ServletException ("Unauthorized access, unable to forward to login page");
}
} catch (IOException io) {
System.out.println("IOException raised in AuthenticationFilter");
} catch (ServletException se) {
System.out.println("ServletException raised in AuthenticationFilter");
}
System.out.print("Authentication: Response dispatched ...");
}

  • A user token is placed in the session after successful authentication in the servlet. It is removed when the session expires or when the user logs out.
  • The login page should call the servlet for authetication with an identifiable query. In this example, the link in login.jsp's form would probably look like "http://domain/servlet/auth?cLogin". (see below).
  • We check for the token first and if it is valid, we don't care about the query string. This setup helps avoid circular calls (especially since we do a forward as opposed to a redirect).
  • Authentication is valid only when a user token exists in the session. But we let the request proceed if the user is logging in.
  • If authentication fails and we have a valid configuration, we simply forward the request to the login page again. Otherwise, we throw an exception (we should really never get here ...). The default error page for the application would likely pick that up.
Web.xml
Add to the root <app> node.
<context-param>
<param-name>loginPage</param-name>
<param-value>/login.jsp</param-value>
</context-param>

<filter>
<filter-name>AuthFilter</filter-name>
<filter-class>com.package.AuthFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AuthFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Code snippet for Login Page
<%
String urlParams = url + "/auth?cLogin";
%>
<form action="<%=urlParams%>" method="POST">
// Form components
</form>

Code snippet for Servlet:
- Obviously would be called from a doPost() method that's already extracted the parameters from the request if one of the parameters is "cLogin".
private void performLogin(HttpServletRequest request, HttpServletResponse response, String username,
String password, ServletContext context) throws ServletException, IOException {
if((username!=null) & (password!=null)) {
User user = userFacade.findByUsername(username);
if(user!=null) {
UserToken tok = Authentication.authenticateUser(user, username, password);
if (tok != null) {
request.getSession().setAttribute("userToken", tok);
response.sendRedirect(context.getContextPath());
}
} else {
String url = context.getInitParameter("login_page");
if (url != null && !"".equals(url)) {
response.sendRedirect(url);
}
}
} else {
String url = context.getInitParameter(
"login_page");
if (url != null && !"".equals(url)) {
response.sendRedirect(url);
}
}
}

That's it. After you deploy a web application with this filter, all requests will be intercepted and authentication checked.