Java Web filter with Ajax requests

Goal

To have a Web filter class that detects expired sessions and redirects the user to the timeout page, even for Ajax requests

Description

I have been working on a project where I implemented a web filter (@WebFilter – see the following snippet for a better look of how that class seemed like at first) class to detect session expiration and to redirect the user to a timeout expired page. Everything was working as expected, except when an Ajax request was made or when the login button (which was, originally, an Ajax post!) was pressed. In the former case, we simply had no answer from the page, i.e., the browser would remain on the same page, not letting the user know about the session expiration problem. In the latter case, the behaviour was the same but, after we made a regular POST (instead of the original Ajax based one), we were redirected to the timeout page (as expected from the code implemented but not expected, when we consider that we were trying to logout from the application, not expecting any type of “error”!).

@WebFilter(filterName = "SessionTimeoutFilter", urlPatterns = "*.jsf")
public class SessionTimeoutFilter implements Filter {

  private String timeoutPage = "timeout.jsf";

  private String loginPage = "login.jsf";

  private String resources = "javax.faces.resource";

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, 
                       FilterChain filterChain) throws IOException, ServletException {
    if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
      HttpServletRequest httpServletRequest = (HttpServletRequest) request;
      HttpServletResponse httpServletResponse = (HttpServletResponse) response;

      // is session expire control required for this request AND is session invalid?
      if (isSessionControlRequiredForThisResource(httpServletRequest) && 
        isSessionInvalid(httpServletRequest)) {
        final String redirectPage = httpServletRequest.getContextPath() + 
                                    "/" + getTimeoutPage());
        httpServletResponse.sendRedirect(redirectPage);
        return;
      } 
    } 	
    filterChain.doFilter(request, response);
  }

  private boolean isSessionControlRequiredForThisResource(
                  HttpServletRequest httpServletRequest) {
    String requestPath = httpServletRequest.getRequestURI();
    return !requestPath.contains(getTimeoutPage()) && 
           !requestPath.contains(getLoginPage()) && !requestPath.contains(resources);
  }

  private boolean isSessionInvalid(HttpServletRequest httpServletRequest) {
    return (httpServletRequest.getRequestedSessionId() != null) && 
           !httpServletRequest.isRequestedSessionIdValid();
  }

  public String getTimeoutPage() {
    return timeoutPage;
  }

  public String getLoginPage() {
    return loginPage;
  }

  @Override
  public void destroy() {
  }
}

How to

The problem was related exactly to the Ajax nature of the request. The request was a partial request but the answer was not a partial response. Therefore, and to solve that specific problem, we had to find a way to return to the browser a partial response, when the request being made was an Ajax one. This has resulted in the following changes:

private boolean isAJAXRequest(HttpServletRequest request) {
  boolean check = false;
  String facesRequest = request.getHeader("Faces-Request");
  if (facesRequest != null && facesRequest.equals("partial/ajax")) {
    check = true;
  }
  return check;
}

The previous method was added and it checks if a specific HTTP request is an Ajax one by checking the header “Faces-Request”. Next, we changed the doFilter() method by adding the call to the previous method inside the if statement for the redirect call, as in:

...
// is session expire control required for this request AND is session invalid?
if (isSessionControlRequiredForThisResource(httpServletRequest) && 
                          isSessionInvalid(httpServletRequest)) {
  final String redirectPage = httpServletRequest.getContextPath() + 
                              "/" + getTimeoutPage());
  if (isAJAXRequest(httpServletRequest)) {
    StringBuilder sb = new StringBuilder();
    sb.append("");
    httpServletResponse.setHeader("Cache-Control", "no-cache");
    httpServletResponse.setCharacterEncoding("UTF-8");
    httpServletResponse.setContentType("text/xml");
    PrintWriter pw = response.getWriter();
    pw.println(sb.toString());
    pw.flush();
    return;
  }
  httpServletResponse.sendRedirect(redirectPage);
  return;
}

With the previous changes we were already able to detect and deal with Ajax calls when the HTTP session had already expired. But we still had the problem of redirecting to the timeout page, even when all we had done was clicking the logout button!

Therefore, we still made two more changes:

  1. Add a parameter to the request when the logout option is invoked, as shown in the following snippet (that code uses a Primefaces commandLink component that invokes the action through an Ajax call)
    ...
    <p:commandLink value="#{messages['menu.logout']}" update="@all"
      process="@this" action="#{login.logout}" global="false">
        <f:param name="logout" value="true" />
    </p:commandLink>
    ...
  2. Add an additional logic to the filter class that checks if the redirect page should be the general timeout page or the login page, as in the following snippet:
    ...
    final String redirectPage = httpServletRequest.getContextPath() + "/"
      + (httpServletRequest.getParameter("logout") != null ? getLoginPage() : getTimeoutPage());
    ...

which took us to the following implementation (full code is shown at this moment):

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 
 * @author Paulo Zenida - Linkare TI
 * 
 */
@WebFilter(filterName = "SessionTimeoutFilter", urlPatterns = "*.jsf")
public class SessionTimeoutFilter implements Filter {

  private String timeoutPage = "timeout.jsf";

  private String loginPage = "login.jsf";

  private String resources = "javax.faces.resource";

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
  }

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, 
                       FilterChain filterChain) throws IOException, ServletException {

    if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) {
      HttpServletRequest httpServletRequest = (HttpServletRequest) request;
      HttpServletResponse httpServletResponse = (HttpServletResponse) response;

      // is session expire control required for this request AND is session invalid?
      if (isSessionControlRequiredForThisResource(httpServletRequest) && 
          isSessionInvalid(httpServletRequest)) {
        final String redirectPage = httpServletRequest.getContextPath() + "/"
	+ (httpServletRequest.getParameter("logout") != null ? getLoginPage() : getTimeoutPage());

	if (isAJAXRequest(httpServletRequest)) {
          StringBuilder sb = new StringBuilder();
	  sb.append("");
	  httpServletResponse.setHeader("Cache-Control", "no-cache");
	  httpServletResponse.setCharacterEncoding("UTF-8");
	  httpServletResponse.setContentType("text/xml");
	  PrintWriter pw = response.getWriter();
	  pw.println(sb.toString());
	  pw.flush();
	  return;
	}

	httpServletResponse.sendRedirect(redirectPage);
	return;
      }
    }
    filterChain.doFilter(request, response);
  }

  private boolean isAJAXRequest(HttpServletRequest request) {
    boolean check = false;
    String facesRequest = request.getHeader("Faces-Request");
    if (facesRequest != null && facesRequest.equals("partial/ajax")) {
      check = true;
    }
    return check;
  }

  /**
   * 
   * session shouldn't be checked for some pages. For example: for timeout page.. 
   * Since we're redirecting to timeout page from this filter, if we don't
   * disable session control for it, filter will again redirect to it and this will be result 
   * with an infinite loop...
   */
  private boolean isSessionControlRequiredForThisResource(HttpServletRequest httpServletRequest) {
    String requestPath = httpServletRequest.getRequestURI();
    return !requestPath.contains(getTimeoutPage()) && 
           !requestPath.contains(getLoginPage()) && 
           !requestPath.contains(resources);
  }

  private boolean isSessionInvalid(HttpServletRequest httpServletRequest) {
    return (httpServletRequest.getRequestedSessionId() != null) && 
           !httpServletRequest.isRequestedSessionIdValid();
  }

  public String getTimeoutPage() {
    return timeoutPage;
  }

  public String getLoginPage() {
    return loginPage;
  }

  @Override
  public void destroy() {
  }
}

Explanations

It seems a lot of work for such a simple thing that should already have been taken care of by some Web framework (I am using CDI/Weld with Primefaces, in that project!). Nevertheless, I could not find a ready made solution for my original problems and therefore, had to take care of that myself. Do you know about other (easier/clever/better) solution? If that is so, please let me know… Thank you in advance! If not, I just hope you enjoy my solution, then…

Advertisements

6 comments

  1. what about using the Omniface’s FullAjaxExceptionHandler and define an error page (your timeout page) in web.xml for the exception type javax.faces.application.ViewExpiredException?
    In addition invalidate the session (FacesContext.getCurrentInstance().getExternalContext().invalidateSession()) in your logout method and let the method return the url of your login page.This works like a charm in my project.

    1. Well, I guess that’s an option too. I may try it one of these days. After all, I’m using Omnifaces in this project as well… Thank you for the tip 🙂

      1. You’re welcome 😉 Re-reading my answer it came to my mind that I forgot to mention that in case you are using a security-constraint, you need to add a form-login-page to web.xml, as well. OmniPartialViewContext is using that page if the security constraint gets triggered on an ajax request. Else a new ViewState is returned and the page won’t change. Check: http://stackoverflow.com/questions/12504131/viewexpiredexception-not-thrown-on-ajax-request-if-jsf-page-is-protected-by-j-se

  2. Hi! I have tested your ‘doFilter’ method, and my brower answer with ‘EmptyResponse’ message. I’m unable to understand how the empty response should help on page redirection.

    I’m using PrimeFaces in my project too.

    1. After a little digging, I used the StringBuilder to send the partial response for Ajax redirection:

      sb.append(“”);

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s