11

Preface

This is my first attempt at a Filter, be gentle.

Project Description

I am trying to finalize a build for a SSO for several of our applications and I seem to be hitting a wall. The webapp I am attempting to connect to uses the "Authentication" header to determine user credentials within the application. I have built a Filter with hopes of setting the header before it is passed on to the webapp.

The Problem

The code passes eclipse validation, compiles, loads to Tomcat, and passes through to the webapp. The only thing that is missing is the Authentication header.

What am I missing/doing wrong?

AuthenticationFilter source

package xxx.xxx.xxx.xxx.filters;

import java.io.IOException;

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.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import xxx.xxx.xxx.ConfigFile;
import xxx.xxx.xxx.Console;
import xxx.xxx.xxx.FalseException;

import xxx.xxx.activity.EncryptUtil;

public class AuthenticationFilter implements Filter {
  public ConfigFile config;

  public void init(FilterConfig arg0) throws ServletException {
    config = new ConfigFile("C:/config.properties");
  }

  public void doFilter(ServletRequest sRequest, ServletResponse sResponse, FilterChain filterChain) throws IOException, ServletException {
    Console.debug("AuthenticationFilter.doFilter() triggered.");
    ServletRequestWrapper request = new ServletRequestWrapper((HttpServletRequest) sRequest);
    HttpServletResponse response = (HttpServletResponse) sResponse;
    HttpSession session = request.getSession();
    Cookie cookie = null;
    try {
      if (request.getParameter("logout") != null) {
        session.invalidate();
        throw new FalseException("Logout recieved");
      }
      String auth = request.getHeader("Authorization");
      if (auth == null) {
        Console.debug("Authorization Header not found.");
        // get cookie --COOKIE NAME--
        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
          throw new FalseException("Cookies not set.");
        }
        for (int i = 0; i < cookies.length; i++) {
          if (cookies[i].getName().equals(config.getProperty("authentication.cookie.name"))) {
            cookie = cookies[i];
          }
        }
        if (cookie == null) {
          throw new FalseException("Cannot find Cookie (" + config.getProperty("authentication.cookie.name") + ") on Client");
        }
        Console.debug("Cookie (" + config.getProperty("authentication.cookie.name") + ") found on Client. value="+cookie.getValue());
        String decToken = decryptToken(cookie.getValue());
        Console.debug("Decrypted Token: "+decToken);

        Console.debug("Setting Authorization Header...");
        request.setAttribute("Authorization", decToken);
        request.addHeader("Authorization", decryptToken(cookie.getValue()));
        Console.debug("Authorization Header set.");
        Console.debug("Validating Authorization Header value: "+request.getHeader("Authorization"));
      }
    }catch (FalseException e) {
      Console.msg(e.getMessage() + ", giving the boot.");
      response.sendRedirect(config.getProperty("application.login.url"));
    } catch (Exception e) {
      Console.error(e);
    }
    Console.debug("AuthenticationFilter.doFilter() finished.");
    filterChain.doFilter(request, response);
  }

  public void destroy() {

  }

  private String decryptToken(String encToken) {
    String token = null;
    token = EncryptUtil.decryptFromString(encToken);
    return token;
  }
}

web.xml source

<web-app>
  <filter>
    <filter-name>AuthenticationFilter</filter-name>
    <display-name>AuthenticationFilter</display-name>
    <description></description>
    <filter-class>com.xxx.xxx.xxx.filters.AuthenticationFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>AuthenticationFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  ...
</web-app>

ServletRequestWrapper Source

package com.xxx.xxx.xxx.filters;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

public class ServletRequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {

  public ServletRequestWrapper(HttpServletRequest request) {
    super(request);
    headerMap = new HashMap();
  }

  private Map headerMap;

  public void addHeader(String name, String value) {
    headerMap.put(name, new String(value));
  }

  public Enumeration getHeaderNames() {
    HttpServletRequest request = (HttpServletRequest) getRequest();
    List list = new ArrayList();
    for (Enumeration e = request.getHeaderNames(); e.hasMoreElements();) {
      list.add(e.nextElement().toString());
    }

    for (Iterator i = headerMap.keySet().iterator(); i.hasNext();) {
      list.add(i.next());
    }
    return Collections.enumeration(list);
  }

  public String getHeader(String name) {
    Object value;
    if ((value = headerMap.get("" + name)) != null)
      return value.toString();
    else
      return ((HttpServletRequest) getRequest()).getHeader(name);
  }

}

Debug Log

LoginServlet.doGet() triggered.
[DEBUG] : Authenticate.isClientLoggedIn() triggered.
xxx url : https://xxx.xxx.xxx/xxx/home.action
[DEBUG] : Authenticate.isClientLoggedIn() status code: 401
Unauthorized User.
Client IS NOT logged in.

-- Fill out Login Form, submit --

LoginServlet.doPost() triggered.
[DEBUG] : Authenticate.isClientLoggedIn() triggered.
xxx url : https://xxx.xxx.xxx./xxx/home.action
[DEBUG] : Authenticate.isClientLoggedIn() status code: 401
Unauthorized User.
Client IS NOT logged in.
Client (--USERID--) attempting basic authentication with password(--PASSWORD--).
[DEBUG] : BasicAuthentication.touch(http://localhost:PORT/vu/loginCheck.html, --USERID--, --PASSWORD--) triggered.
[DEBUG] : BasicAuthentication.touch() response code: 200
Client (--USERID--) has been logged IN.
Client (--USERID--) basic authentication finished, Client is logged in.
Client (--USERID--) logged in successfully.
[DEBUG] : Cookie (xxx_token) Set: 1e426f19ebdfef05dec6544307addc75401ecdc908a3c7e6df5336c744--SECRET--
[DEBUG] : Redirecting client to https://xxx.xxx.xxx/xxx/home.action

-- Redirected to webapp, filter recieves --

[DEBUG] : AuthenticationFilter.doFilter() triggered.
[DEBUG] : Authorization Header not found. << Initical check to see if user is already logged in to site
[DEBUG] : Cookie (xxx_token) found on Client. value=1e426f19ebdfef05dec6544307addc75401ecdc908a3c7e6df5336c744--SECRET--
[DEBUG] : Decrypted Token: Basic --SECRET--
[DEBUG] : Setting Authorization Header...
[DEBUG] : Authorization Header set.
[DEBUG] : Validating Authorization Header value: Basic --SECRET-- << Value matches Decrypted Token
[DEBUG] : AuthenticationFilter.doFilter() finished.

-- Web Application errors out, unable to find Authorization header 

Thanks for your help.

PseudoNinja
  • 2,846
  • 1
  • 27
  • 37
  • 3
    Just some advice; please don't throw Throwable, that is reserved for a very wide rage of errors usually only caught by the JVM as a last resort and never ever catch throwable or exceptions in a filter without rethrowing them. – Andrew White Feb 04 '11 at 15:24
  • @Andrew White, I will keep that in mind. I was trying to keep my process and errors separate without a bunch of ugly if statements. It doesn't excuse my behavior, just explains it. – PseudoNinja Feb 04 '11 at 15:29
  • You said that this compiles, but I can't find an addHeader() method on ServletRequestWrapper. – highlycaffeinated Feb 04 '11 at 15:34
  • @highlycaffeinated: Sorry custom wrapper class I didn't include in post. I will add it now. – PseudoNinja Feb 04 '11 at 15:37
  • Your phrase "The webapp I am attempting to connect" is ambiguous. Elaborate in detail. For instance, I interpreted it as using `URL#openConnection()`, but that would obviously not work together with your filter (and that might be the root cause of your problem). – BalusC Feb 04 '11 at 15:42
  • @BalusC I have an existing web application running on Tomcat 5.5. In its current release it uses basic authentication to validate user credentials (hence the Authorization header). This app parses the Authorization header in order to determine the user logged in (Don't ask me, didn't write it). – PseudoNinja Feb 04 '11 at 15:56
  • 1
    OK, how exactly did you conclude that the header is missing? Did you debug `request.getHeader()` in the webapp of interest? Is the request the webapp is receiving an instance of `ServletRequestWrapper`? – BalusC Feb 04 '11 at 16:00
  • @BalusC The header is missing in Fiddler, Firebug(net), and the webapp errors out because it cannot find the user credentials. Yes the request it is receiving is an instance of the ServletRequestWrapper. – PseudoNinja Feb 04 '11 at 16:12
  • Truly the header is missing there, because the header is not set by the client but by yourself in server. Add a breakpoint on `getHeader()` of your wrapper and debug. – BalusC Feb 04 '11 at 16:14
  • @BalusC: Its stepping through correctly however when it checks for the cookie it throws me out because the cookie doesnt exist in the application because it was not referenced by the SSO. Is there a way to manually set a cookie inside eclipse debug? – PseudoNinja Feb 04 '11 at 19:12
  • Can't you just send a request with the cookie? Where are you testing this then? Or does your local/test environment not match the production environment? Anyway, you could modify request headers with Firefox tamperdata plugin. – BalusC Feb 07 '11 at 16:36
  • In your question, can you please clearly state the expected result instead of the actual result you are getting? Actual println or log statements in your code would be helpful, with an explanation of how they do not match what you expect. – Jesse Barnum Feb 07 '11 at 16:46
  • @Jesse Barnum: Log Files have been appended. The Authorization Header and cookie both seem to be working correctly into the filter, but the Authorization header being set does not seem to stick through to the servlet. – PseudoNinja Feb 07 '11 at 22:48

2 Answers2

11

I'm adding a new answer, since it's completely different.

I did a test on my system. I copied your code, dumped the cookie test, and wrote a simple Servlet to dump things out for me.

And it worked fine, save for one caveat.

I don't know how your app is using this. But your ServletRequestWrapper implements getHeaderNames, and getHeader, but it does NOT implement getHeaders. I ran in to that problem as I used getHeaders to try and dump the request, and, of course, Authorization was missing.

So, you may want to look at your code closer to see if it is indeed not using getHeaders. If it is, it will "work fine", but completely skip the work you've done, and thus miss your Authorization header.

Here's my implementation, and it worked for me.

    @Override
    public Enumeration getHeaders(String name) {
        Enumeration e = super.getHeaders(name);
        if (e != null && e.hasMoreElements()) {
            return e;
        } else {
            List l = new ArrayList();
            if (headerMap.get(name) != null) {
                l.add(headerMap.get(name));
            }
            return Collections.enumeration(l);
        }
    }
bluish
  • 26,356
  • 27
  • 122
  • 180
Will Hartung
  • 115,893
  • 19
  • 128
  • 203
1

First, the most basic question (kind of an "is this plugged in" question), I assume that your cookies are all rooted in the same domain, and that you're not trying to get cross domain behavior here. Because cookies won't do that.

Beyond the cookie test, this looks fine. But it all hinges on the cookie test.

If you want to test the Authorization header, then you can simply short circuit the cookie test (i.e. it always passes) and populate the Authorization header with some valid value. This will, in the short term, test your whole Authorization scheme.

Once that's done/fixed, then you can focus on the cookie setting and delivery.

I also assume that you're not using Java EE Container based authentication, with Tomcat doing this check for you. In that case, a filter is simply "too late". The container will have already made it's decisions before your filter even gets called.

If you are using container based authentication, and your apps are on the same container, I would imagine Tomcat (or someone) has an SSO option at the container level. I know that Glassfish will do this for you out of the box. It should be straightforward to modify Tomcat artifacts (i.e. not portable Java EE/Servlet mechanisms) to implement this if that is the case.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
Will Hartung
  • 115,893
  • 19
  • 128
  • 203
  • Good answer Will. I would also wish to point out that it is security wise advisable to use the application server provided facilities for authentication and SSO. They are probably better tested and more widely scrutinized. I am especially worried about the EncryptionUtils mentioned. – erloewe Feb 07 '11 at 17:54
  • Normally I would agree with you guys but the project requirements state that the Login must support existing user management which just happens to be Basic Authentication. The Application itself uses the information in the header to populate information on the site. – PseudoNinja Feb 07 '11 at 22:45
  • I wasn't suggesting you change to Container based authentication. But, to be clear, the containers DO support Basic authentication. But you would have to change your application to use the Servlet security interface (isUserInRole, getPrincipal, etc.). – Will Hartung Feb 07 '11 at 22:58
  • And that is the reason for the filter, because I cannot (easily) change the app. Eventually the project will move to Hash table but in the mean time here we are. – PseudoNinja Feb 08 '11 at 16:09