Milen Dyankov

Software Engineer, Consultant, Developer Advocate

ATG session tracking cookies and subdomains.

January 29, 2009 | 5 Minute Read

If an ATG based web application is available under few subdomains (domain.com, www.domain.com, shop.domain.com) keeping track of session cookies across subdomains may be a challenge. Session tracking cookies (like jsessionid) usually do not have domain property set, which means they are sent back to exactly the same host they came from. So if visitors switch to another subdomain while navigating through the application they would most likely end up having a new session. Depending on what information session holds, the number of visitors and how many simultaneous sessions the server can handle, this may or may not be a problem.

The best solution is obviously not to let your visitors change the domain while browsing you site (for example by using relative links only). But if your application occupies the whole domain it may be easier and safer to set the domain property for all relevant cookies. Depending on what ATG modules you use, a number of cookies used for session tracking may vary.

Here is how this can be done on ATG 2006.3 running on JBoss 4.0.3.Since JBoss uses Tomcat as web container, we will add a valve to server.xml in which we'll replace a original HttpServeltResponse with custom wrapper. Actually this is a slightly modified version of the solution described here https://jira.jboss.org/jira/browse/JBWEB-107. So here is our valve:

public class CookieRewriteValve extends ValveBase {
    private String domain = null;
    private String cookieNames = null;
    private Set cookiesToModify = null;

    public void postRegister(Boolean registrationDone) {
        if (registrationDone.booleanValue()) {
            if (cookieNames != null && cookieNames.trim().length() > 0) {
                cookiesToModify = new HashSet(Arrays.asList(cookieNames.toUpperCase().split(",\\s*")));
            }
        }
    }

    public void invoke(Request request, Response response) throws IOException, ServletException {
        CookieModifier.createThreadInstance(cookiesToModify, path, domain, secure, maxAge);
        response = new CookieRewriteResponseWrapper(response);
        request.setResponse(response);
        getNext().invoke(request, response);
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    public String getCookieNames() {
        return cookieNames;
    }

    public void setCookieNames(String cookieNames) {
        this.cookieNames = cookieNames;
    }
}

It has to be registered in "Engine" section of <PATH_TO_JBOSS>/server/<SERVER_NAME>/deploy/jbossweb-tomcat55.sar/server.xml like this

<Valve domain=".domain.com" cookieNames="jsessionid, ATG_SESSION_ID" className="cookie.rewrite.example.CookieRewriteValve"/>

At startup engine initializes declared valves and calls postRegister method on each. This is where we read, parse and store arguments. Later on the invoke method is called by the engine on every incoming request. This is where we create a new thread local instance of CookieModifier and wrap the original Response in CookieRewriteResponseWrapper. This wrapper is constructed by passing a reference to the original Response:

public class CookieRewriteResponseWrapper extends org.apache.catalina.connector.Response

    protected Response res;

    public CookieRewriteResponseWrapper(Response res) {
        this.res = res;
    }
    ....

It also overwrites EVERY public method from org.apache.catalina.connector.Response like this:

    public returnType methodName (parameters...) {
        return res.methodName (parameters...)
    }

except the addCookie(Cookie cookie) method which tries to get an instance of CookieModifier and use it to modify the cookie before it delegates the request to the original Response object:

    public void addCookie(Cookie cookie) {
        CookieModifier cookieModifier = CookieModifier.getInstance();
        if (cookieModifier != null) {
            cookieModifier.modify(cookie);
        }
        res.addCookie(cookie);
    }

Finally here is how CookieModifier looks like:

public class CookieModifier {

    protected String cookieDomain;
    protected Set cookiesToModify = null;
    protected boolean modifyAll = false;

    private static ThreadLocal threadInstance = new ThreadLocal();

    private CookieModifier(Set cookiesToModify, String cookieDomain) {
        this.cookieDomain = cookieDomain;
        this.cookiesToModify = cookiesToModify;
        if (cookiesToModify == null || cookiesToModify.isEmpty()) modifyAll = true;
    }

    public static void createThreadInstance(Set cookiesToModify, String cookieDomain) {
        threadInstance.set(new CookieModifier(cookiesToModify, cookieDomain));
    }

    public static CookieModifier getInstance() {
        return (CookieModifier) threadInstance.get();
    }

    public void modify(Cookie cookie) {
        if (modifyAll || cookiesToModify.contains(cookie.getName()))
            if (cookieDomain != null) cookie.setDomain(cookieDomain);
        }
    }

}

Note the static methods used to store a new instance of this class in current thread.  It will become clear why this is important later.

So all we have to do now is to pack these classes in a jar file, place it in <PATH_TO_JBOSS>/server/<SERVER_NAME>/lib and add the valve line to server.xml. But if you try to run this, you will notice that while it works for "jsessionid" it does not for "ATG_SESSION_ID". This is because ATG itself wraps the Response object in DynamoHttpServletResponse which apparently does not make use of the original addCookie method! Fortunately though, ATG allows user to have custom implementations of DynamoHttpServletResponse. To tell ATG we have a custom implementation we need to add the following configuration to /atg/dynamo/servlet/dafpipeline/DynamoHandler component:

responseClass=cookie.rewrite.example.DynamoHttpServletResponseWrapper

Now when ATG needs to create DynamoHttpServletResponse it will actually create an instance of DynamoHttpServletResponseWrapper. But how can our implementation obtain access to appropriately configured instance of CookieModifier? It can do so because our CookieRewriteValve, executed earlier in the same thread, placed an instance of CookieModifier in thread local variable. Here is how DynamoHttpServletResponseWrapper looks like:

public class DynamoHttpServletResponseWrapper extends DynamoHttpServletResponse {

    public void addCookie(Cookie cookie) {
        CookieModifier cookieModifier = CookieModifier.getInstance();
        if (cookieModifier != null) {
            cookieModifier.modify(cookie);
        }
        super.addCookie(cookie);
    }
}

That's it.  Once we add this class to the jar created earlier and restart JBoss it should work like a charm ;)