You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tomcat.apache.org by Zach Cox <zc...@gmail.com> on 2008/04/19 22:25:51 UTC

Share session cookie across subdomains

I'm working on a site that has a single web app in Tomcat that handles
lots of subdomains via wildcard DNS:
 - site.com
 - www.site.com
 - sub1.site.com
 - sub2.site.com
 - etc.

Basically, the site content gets customized based on the subdomain in
the request, if any.  However I ran into a problem where Tomcat would
use separate JSESSIONID session cookies on each different domain which
causes problems as the user would have multiple sessions if they
switched subdomains.

I found an examle Valve at http://www.esus.be/blog/?p=3 but it
ultimately did not result in a cross-subdomain session cookie (at
least in the Tomcat 6.0.14 I'm using).  I'm posting the Valve that did
actually seem to solve the problem for me below in hopes that it will
help someone else with the same problem in the future.

Basically you end up with a single session cookie with its domain set
to .site.com so the client will send it back in the request for any
subdomain.

Usage:
 - compile CrossSubdomainSessionValve & put it in a .jar file
 - put that .jar file in $CATALINA_HOME/lib directory
 - include a <Valve
className="org.three3s.valves.CrossSubdomainSessionValve"/> in
$CATALINA_HOME/conf/server.xml

If you see/experience any problems please let me know!

Thanks,
Zach


package org.three3s.valves;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.catalina.*;
import org.apache.catalina.connector.*;
import org.apache.catalina.valves.*;
import org.apache.tomcat.util.buf.*;
import org.apache.tomcat.util.http.*;

/** <p>Replaces the domain of the session cookie generated by Tomcat
with a domain that allows that
 * session cookie to be shared across subdomains.  This valve digs
down into the response headers
 * and replaces the Set-Cookie header for the session cookie, instead
of futilely trying to
 * modify an existing Cookie object like the example at
http://www.esus.be/blog/?p=3.  That
 * approach does not work (at least as of Tomcat 6.0.14) because the
 * <code>org.apache.catalina.connector.Response.addCookieInternal</code>
method renders the
 * cookie into the Set-Cookie response header immediately, making any
subsequent modifying calls
 * on the Cookie object ultimately pointless.</p>
 *
 * <p>This results in a single, cross-subdomain session cookie on the
client that allows the
 * session to be shared across all subdomains.  However, see the
{@link getCookieDomain(Request)}
 * method for limits on the subdomains.</p>
 *
 * <p>Note though, that this approach will fail if the response has
already been committed.  Thus,
 * this valve forces Tomcat to generate the session cookie and then
replaces it before invoking
 * the next valve in the chain.  Hopefully this is early enough in the
valve-processing chain
 * that the response will not have already been committed.  You are
advised to define this
 * valve as early as possible in server.xml to ensure that the
response has not already been
 * committed when this valve is invoked.</p>
 *
 * <p>We recommend that you define this valve in server.xml
immediately after the Catalina Engine
 * as follows:
 * <pre>
 * &lt;Engine name="Catalina"...&gt;
 *     &lt;Valve className="org.three3s.valves.CrossSubdomainSessionValve"/&gt;
 * </pre>
 * </p>
 */
public class CrossSubdomainSessionValve extends ValveBase
{
    public CrossSubdomainSessionValve()
    {
        super();
        info = "org.three3s.valves.CrossSubdomainSessionValve/1.0";
    }

    @Override
    public void invoke(Request request, Response response) throws
IOException, ServletException
    {
        //this will cause Request.doGetSession to create the session
cookie if necessary
        request.getSession(true);

        //replace any Tomcat-generated session cookies with our own
        Cookie[] cookies = response.getCookies();
        if (cookies != null)
        {
            for (int i = 0; i < cookies.length; i++)
            {
                Cookie cookie = cookies[i];
                containerLog.debug("CrossSubdomainSessionValve: Cookie
name is " + cookie.getName());
                if (Globals.SESSION_COOKIE_NAME.equals(cookie.getName()))
                    replaceCookie(request, response, cookie);
            }
        }

        //process the next valve
        getNext().invoke(request, response);
    }

    /** Replaces the value of the response header used to set the
specified cookie to a value
     * with the cookie's domain set to the value returned by
<code>getCookieDomain(request)</code>
     *
     * @param request
     * @param response
     * @param cookie cookie to be replaced.
     */
    @SuppressWarnings("unchecked")
    protected void replaceCookie(Request request, Response response,
Cookie cookie)
    {
        //copy the existing session cookie, but use a different domain
        Cookie newCookie = new Cookie(cookie.getName(), cookie.getValue());
        if (cookie.getPath() != null)
            newCookie.setPath(cookie.getPath());
        newCookie.setDomain(getCookieDomain(request));
        newCookie.setMaxAge(cookie.getMaxAge());
        newCookie.setVersion(cookie.getVersion());
        if (cookie.getComment() != null)
            newCookie.setComment(cookie.getComment());
        newCookie.setSecure(cookie.getSecure());

        //if the response has already been committed, our replacement
strategy will have no effect
        if (response.isCommitted())
            containerLog.error("CrossSubdomainSessionValve: response
was already committed!");

        //find the Set-Cookie header for the existing cookie and
replace its value with new cookie
        MimeHeaders headers = response.getCoyoteResponse().getMimeHeaders();
        for (int i = 0, size = headers.size(); i < size; i++)
        {
            if (headers.getName(i).equals("Set-Cookie"))
            {
                MessageBytes value = headers.getValue(i);
                if (value.indexOf(cookie.getName()) >= 0)
                {
                    StringBuffer buffer = new StringBuffer();
                    ServerCookie.appendCookieValue(buffer,
newCookie.getVersion(), newCookie
                            .getName(), newCookie.getValue(),
newCookie.getPath(), newCookie
                            .getDomain(), newCookie.getComment(),
newCookie.getMaxAge(), newCookie
                            .getSecure());
                    containerLog.debug("CrossSubdomainSessionValve:
old Set-Cookie value: "
                            + value.toString());
                    containerLog.debug("CrossSubdomainSessionValve:
new Set-Cookie value: " + buffer);
                    value.setString(buffer.toString());
                }
            }
        }
    }

    /** Returns the last two parts of the specified request's server
name preceded by a dot.
     * Using this as the session cookie's domain allows the session to
be shared across subdomains.
     * Note that this implies the session can only be used with
domains consisting of two or
     * three parts, according to the domain-matching rules specified
in RFC 2109 and RFC 2965.
     *
     * <p>Examples:</p>
     * <ul>
     * <li>foo.com => .foo.com</li>
     * <li>www.foo.com => .foo.com</li>
     * <li>bar.foo.com => .foo.com</li>
     * <li>abc.bar.foo.com => .foo.com - this means cookie won't work
on abc.bar.foo.com!</li>
     * </ul>
     *
     * @param request provides the server name used to create cookie domain.
     * @return the last two parts of the specified request's server
name preceded by a dot.
     */
    protected String getCookieDomain(Request request)
    {
        String cookieDomain = request.getServerName();
        String[] parts = cookieDomain.split("\\.");
        if (parts.length >= 2)
            cookieDomain = parts[parts.length - 2] + "." +
parts[parts.length - 1];
        return "." + cookieDomain;
    }

    public String toString()
    {
        return ("CrossSubdomainSessionValve[container=" +
container.getName() + ']');
    }
}

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Zach Cox <zc...@gmail.com>.
That's a really good idea and could definitely be done by modifying the
getCookieDomain method.  I whipped that method up pretty quickly for my own
purposes - and you're correct, this currently won't work with two-part TLDs
like .co.uk.  If I get some spare time (already used 2 days of this project
creating the existing valve) I'll add that.  But I think there should be 3
use cases in server.xml:

1) Default - just assume 2 parts in cookie domain to extract from server
name, like .domain.com:

<Valve class="className="org.three3s.valves.CrossSubdomainSessionValve" />

2) Specify number of parts in cookie domain to support .domain.co.uk:

<Valve class="className="org.three3s.valves.CrossSubdomainSessionValve"
partCount="3" />

#2 could also be used to support multi-level subdomains like a.b.domain.com. 
RFCs 2109 & 2965 say that a cookie with domain .domain.com will not be sent
in a request to a.b.domain.com; instead it would need to be .b.domain.com. 
I haven't tested that so I don't know if that's accurate in practice or not.

You'd need to be careful though if your app supports subdomains with
different levels, like a.b.domain.com and c.domain.com etc.  You may end up
with session cookies for .b.domain.com and .domain.com and I haven't thought
enough about if there would be confusion in a request for c.domain.com or
b.comain.com.

3) Specify the actual cookie domain to use:

<Valve class="className="org.three3s.valves.CrossSubdomainSessionValve"
cookieDomain=".domain.com" />

So to support #2 and #3 CrossSubdomainSessionValve would need partCount and
cookieDomain fields with get/set methods and would then use those, if
specified, in the getCookieDomain method.  Until I have time to implement &
test I will leave this as an exercise to the reader.  ;)
-- 
View this message in context: http://www.nabble.com/Share-session-cookie-across-subdomains-tp16787390p16793243.html
Sent from the Tomcat - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Pid <p...@pidster.com>.
how about adding either, a property that allows you to specify the 
number of domain parts (accounting for domain.co.uk for example), or the 
actual domain?


p





Zach Cox wrote:
> I'm working on a site that has a single web app in Tomcat that handles
> lots of subdomains via wildcard DNS:
>  - site.com
>  - www.site.com
>  - sub1.site.com
>  - sub2.site.com
>  - etc.
> 
> Basically, the site content gets customized based on the subdomain in
> the request, if any.  However I ran into a problem where Tomcat would
> use separate JSESSIONID session cookies on each different domain which
> causes problems as the user would have multiple sessions if they
> switched subdomains.
> 
> I found an examle Valve at http://www.esus.be/blog/?p=3 but it
> ultimately did not result in a cross-subdomain session cookie (at
> least in the Tomcat 6.0.14 I'm using).  I'm posting the Valve that did
> actually seem to solve the problem for me below in hopes that it will
> help someone else with the same problem in the future.
> 
> Basically you end up with a single session cookie with its domain set
> to .site.com so the client will send it back in the request for any
> subdomain.
> 
> Usage:
>  - compile CrossSubdomainSessionValve & put it in a .jar file
>  - put that .jar file in $CATALINA_HOME/lib directory
>  - include a <Valve
> className="org.three3s.valves.CrossSubdomainSessionValve"/> in
> $CATALINA_HOME/conf/server.xml
> 
> If you see/experience any problems please let me know!
> 
> Thanks,
> Zach
> 
> 
> package org.three3s.valves;
> 
> import java.io.*;
> 
> import javax.servlet.*;
> import javax.servlet.http.*;
> 
> import org.apache.catalina.*;
> import org.apache.catalina.connector.*;
> import org.apache.catalina.valves.*;
> import org.apache.tomcat.util.buf.*;
> import org.apache.tomcat.util.http.*;
> 
> /** <p>Replaces the domain of the session cookie generated by Tomcat
> with a domain that allows that
>  * session cookie to be shared across subdomains.  This valve digs
> down into the response headers
>  * and replaces the Set-Cookie header for the session cookie, instead
> of futilely trying to
>  * modify an existing Cookie object like the example at
> http://www.esus.be/blog/?p=3.  That
>  * approach does not work (at least as of Tomcat 6.0.14) because the
>  * <code>org.apache.catalina.connector.Response.addCookieInternal</code>
> method renders the
>  * cookie into the Set-Cookie response header immediately, making any
> subsequent modifying calls
>  * on the Cookie object ultimately pointless.</p>
>  *
>  * <p>This results in a single, cross-subdomain session cookie on the
> client that allows the
>  * session to be shared across all subdomains.  However, see the
> {@link getCookieDomain(Request)}
>  * method for limits on the subdomains.</p>
>  *
>  * <p>Note though, that this approach will fail if the response has
> already been committed.  Thus,
>  * this valve forces Tomcat to generate the session cookie and then
> replaces it before invoking
>  * the next valve in the chain.  Hopefully this is early enough in the
> valve-processing chain
>  * that the response will not have already been committed.  You are
> advised to define this
>  * valve as early as possible in server.xml to ensure that the
> response has not already been
>  * committed when this valve is invoked.</p>
>  *
>  * <p>We recommend that you define this valve in server.xml
> immediately after the Catalina Engine
>  * as follows:
>  * <pre>
>  * &lt;Engine name="Catalina"...&gt;
>  *     &lt;Valve className="org.three3s.valves.CrossSubdomainSessionValve"/&gt;
>  * </pre>
>  * </p>
>  */
> public class CrossSubdomainSessionValve extends ValveBase
> {
>     public CrossSubdomainSessionValve()
>     {
>         super();
>         info = "org.three3s.valves.CrossSubdomainSessionValve/1.0";
>     }
> 
>     @Override
>     public void invoke(Request request, Response response) throws
> IOException, ServletException
>     {
>         //this will cause Request.doGetSession to create the session
> cookie if necessary
>         request.getSession(true);
> 
>         //replace any Tomcat-generated session cookies with our own
>         Cookie[] cookies = response.getCookies();
>         if (cookies != null)
>         {
>             for (int i = 0; i < cookies.length; i++)
>             {
>                 Cookie cookie = cookies[i];
>                 containerLog.debug("CrossSubdomainSessionValve: Cookie
> name is " + cookie.getName());
>                 if (Globals.SESSION_COOKIE_NAME.equals(cookie.getName()))
>                     replaceCookie(request, response, cookie);
>             }
>         }
> 
>         //process the next valve
>         getNext().invoke(request, response);
>     }
> 
>     /** Replaces the value of the response header used to set the
> specified cookie to a value
>      * with the cookie's domain set to the value returned by
> <code>getCookieDomain(request)</code>
>      *
>      * @param request
>      * @param response
>      * @param cookie cookie to be replaced.
>      */
>     @SuppressWarnings("unchecked")
>     protected void replaceCookie(Request request, Response response,
> Cookie cookie)
>     {
>         //copy the existing session cookie, but use a different domain
>         Cookie newCookie = new Cookie(cookie.getName(), cookie.getValue());
>         if (cookie.getPath() != null)
>             newCookie.setPath(cookie.getPath());
>         newCookie.setDomain(getCookieDomain(request));
>         newCookie.setMaxAge(cookie.getMaxAge());
>         newCookie.setVersion(cookie.getVersion());
>         if (cookie.getComment() != null)
>             newCookie.setComment(cookie.getComment());
>         newCookie.setSecure(cookie.getSecure());
> 
>         //if the response has already been committed, our replacement
> strategy will have no effect
>         if (response.isCommitted())
>             containerLog.error("CrossSubdomainSessionValve: response
> was already committed!");
> 
>         //find the Set-Cookie header for the existing cookie and
> replace its value with new cookie
>         MimeHeaders headers = response.getCoyoteResponse().getMimeHeaders();
>         for (int i = 0, size = headers.size(); i < size; i++)
>         {
>             if (headers.getName(i).equals("Set-Cookie"))
>             {
>                 MessageBytes value = headers.getValue(i);
>                 if (value.indexOf(cookie.getName()) >= 0)
>                 {
>                     StringBuffer buffer = new StringBuffer();
>                     ServerCookie.appendCookieValue(buffer,
> newCookie.getVersion(), newCookie
>                             .getName(), newCookie.getValue(),
> newCookie.getPath(), newCookie
>                             .getDomain(), newCookie.getComment(),
> newCookie.getMaxAge(), newCookie
>                             .getSecure());
>                     containerLog.debug("CrossSubdomainSessionValve:
> old Set-Cookie value: "
>                             + value.toString());
>                     containerLog.debug("CrossSubdomainSessionValve:
> new Set-Cookie value: " + buffer);
>                     value.setString(buffer.toString());
>                 }
>             }
>         }
>     }
> 
>     /** Returns the last two parts of the specified request's server
> name preceded by a dot.
>      * Using this as the session cookie's domain allows the session to
> be shared across subdomains.
>      * Note that this implies the session can only be used with
> domains consisting of two or
>      * three parts, according to the domain-matching rules specified
> in RFC 2109 and RFC 2965.
>      *
>      * <p>Examples:</p>
>      * <ul>
>      * <li>foo.com => .foo.com</li>
>      * <li>www.foo.com => .foo.com</li>
>      * <li>bar.foo.com => .foo.com</li>
>      * <li>abc.bar.foo.com => .foo.com - this means cookie won't work
> on abc.bar.foo.com!</li>
>      * </ul>
>      *
>      * @param request provides the server name used to create cookie domain.
>      * @return the last two parts of the specified request's server
> name preceded by a dot.
>      */
>     protected String getCookieDomain(Request request)
>     {
>         String cookieDomain = request.getServerName();
>         String[] parts = cookieDomain.split("\\.");
>         if (parts.length >= 2)
>             cookieDomain = parts[parts.length - 2] + "." +
> parts[parts.length - 1];
>         return "." + cookieDomain;
>     }
> 
>     public String toString()
>     {
>         return ("CrossSubdomainSessionValve[container=" +
> container.getName() + ']');
>     }
> }
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Zach Cox <zc...@gmail.com>.
Again, I'll point out that Tomcat's Request class is not designed properly
for extension and offer these two proposed solutions, both of which would
need to be implemented in core Tomcat source code by Tomcat developers:

1) Let users specify the fully-qualified name of a Request subclass in
server.xml, then instantiate that class any time a Request object is needed. 
Maybe something like this:

<Request class="com.whatever.MyRequestSubclass" />

Internally, there could be a RequestFactory that any client could get a new
Request object from.  It would either instantiate Request directly if no
<Request> element was specified in server.xml, or instaniate a Request
subclass if one was specified.

2) Provide a copy constructor in the Request class so that Request can be
properly subclassed and any Valve can wrap the Request instance passed-in to
the invoke() method with an instance of the subclass to pass downstream. 
This is the constructor that would be needed:

public Request(Request request)

This constructor would copy all of the passed-in request's state into its
own state, so that this new Request instance can be used further down the
pipeline exactly as the passed-in request would have been.

Thanks,
Zach



Christopher Schultz-2 wrote:
> 
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> Zach,
> 
> Zach Cox wrote:
> | Hi Matthias - Unfortunately I think the Response class is just as
> ill-suited
> | for wrapping/extending as the Request class is.  Which boggles my mind -
> | isn't wrapping the Request and/or Response one of the primary intended
> | usages of Filters & Valves?
> 
> Filters, yes. Apparently, not Valves :(
> 
> I wonder if the Tomcat devs would accept a patch to change this.
> 
> - -chris
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (MingW32)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
> 
> iEYEARECAAYFAkgR3IUACgkQ9CaO5/Lv0PAKGwCfYGKXcA0VEKLLkp2ilAnlYy/E
> rtwAoKCxanMCJbnW/zG3wd16254bnWX/
> =1HA0
> -----END PGP SIGNATURE-----
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/Share-session-cookie-across-subdomains-tp16787390p16898364.html
Sent from the Tomcat - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Zach,

Zach Cox wrote:
| Hi Matthias - Unfortunately I think the Response class is just as
ill-suited
| for wrapping/extending as the Request class is.  Which boggles my mind -
| isn't wrapping the Request and/or Response one of the primary intended
| usages of Filters & Valves?

Filters, yes. Apparently, not Valves :(

I wonder if the Tomcat devs would accept a patch to change this.

- -chris
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgR3IUACgkQ9CaO5/Lv0PAKGwCfYGKXcA0VEKLLkp2ilAnlYy/E
rtwAoKCxanMCJbnW/zG3wd16254bnWX/
=1HA0
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Zach,

Zach Cox wrote:
| Hi Matthias - Unfortunately I think the Response class is just as
ill-suited
| for wrapping/extending as the Request class is.

Okay, I finally looked at this class, again. static final is just fine:
it doesn't mess with the state of the object, so you can ignore the
method for extension purposes.

https://issues.apache.org/bugzilla/show_bug.cgi?id=45014

You can use the wrapper classes I generated and attached to this bug, or
you can wait to see if anyone incorporates them into Tomcat. I can't see
why you couldn't use these to meet your needs.

- -chris

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgss+sACgkQ9CaO5/Lv0PDYaACeLQkperBElXOBlNPIjOWnUfhq
58QAn3TzAT3comZcPlsaA23LOkBN71Rj
=t3BF
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


RE: Share session cookie across subdomains

Posted by Zach Cox <zc...@gmail.com>.
Hi Matthias - Unfortunately I think the Response class is just as ill-suited
for wrapping/extending as the Request class is.  Which boggles my mind -
isn't wrapping the Request and/or Response one of the primary intended
usages of Filters & Valves?  The Servlet API provides
HttpServletRequestWrapper and HttpServletResponseWrapper just for that
purpose.  But because Tomcat adds a ton of extra stuff in their Request &
Response implementations those wrappers are useless.

Thanks,
Zach



Reich, Matthias wrote:
> 
> Hi,
> 
> I haven't thought about the idea in detail, but would it be possible to
> wrap the response?
> The wrapper would have to ensure that cookie replacement is applied
> before any data is written to the output stream or writer.
> A remaining issue would be that encoded (redirect-) URLs in the response
> must also be modified.
> 
> -- Matthias
> 
> -----Original Message-----
> From: Christopher Schultz [mailto:chris@christopherschultz.net] 
> Sent: Wednesday, April 23, 2008 9:51 PM
> To: Tomcat Users List
> Subject: Re: Share session cookie across subdomains
> 
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> Zach,
> 
> Zach Cox wrote:
> | Hi Chris - I'm not sure what this will accomplish:
> |
> | public Request(Request wrapped)
> | {
> | ~    this.wrapped = wrapped;
> | }
> |
> | public whatever doWhatever(args)
> | {
> | ~    super.doWhatever(args);
> | }
> |
> | 1) this.wrapped is never used so what's the point of saving it?
> 
> Sorry, I meant "wrapper.doWhatever(args)";
> 
> I've taken a closer look at this class and my suggestion is impossible
> as there is a final method in the class. :(
> 
> Looks like valves are not nearly as useful as they should be. double :(
> 
> - -chris
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (MingW32)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
> 
> iEYEARECAAYFAkgPkzoACgkQ9CaO5/Lv0PB1fgCgutaBFqLgjPK2H16yWJ04YxAn
> D3oAn0eLFzc8sHiWft3yKvtxFIbRLryI
> =hwi6
> -----END PGP SIGNATURE-----
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/Share-session-cookie-across-subdomains-tp16787390p16894923.html
Sent from the Tomcat - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


RE: Share session cookie across subdomains

Posted by "Reich, Matthias" <ma...@siemens.com>.
Hi,

I haven't thought about the idea in detail, but would it be possible to
wrap the response?
The wrapper would have to ensure that cookie replacement is applied
before any data is written to the output stream or writer.
A remaining issue would be that encoded (redirect-) URLs in the response
must also be modified.

-- Matthias

-----Original Message-----
From: Christopher Schultz [mailto:chris@christopherschultz.net] 
Sent: Wednesday, April 23, 2008 9:51 PM
To: Tomcat Users List
Subject: Re: Share session cookie across subdomains

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Zach,

Zach Cox wrote:
| Hi Chris - I'm not sure what this will accomplish:
|
| public Request(Request wrapped)
| {
| ~    this.wrapped = wrapped;
| }
|
| public whatever doWhatever(args)
| {
| ~    super.doWhatever(args);
| }
|
| 1) this.wrapped is never used so what's the point of saving it?

Sorry, I meant "wrapper.doWhatever(args)";

I've taken a closer look at this class and my suggestion is impossible
as there is a final method in the class. :(

Looks like valves are not nearly as useful as they should be. double :(

- -chris
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgPkzoACgkQ9CaO5/Lv0PB1fgCgutaBFqLgjPK2H16yWJ04YxAn
D3oAn0eLFzc8sHiWft3yKvtxFIbRLryI
=hwi6
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Zach,

Zach Cox wrote:
| Hi Chris - I'm not sure what this will accomplish:
|
| public Request(Request wrapped)
| {
| ~    this.wrapped = wrapped;
| }
|
| public whatever doWhatever(args)
| {
| ~    super.doWhatever(args);
| }
|
| 1) this.wrapped is never used so what's the point of saving it?

Sorry, I meant "wrapper.doWhatever(args)";

I've taken a closer look at this class and my suggestion is impossible
as there is a final method in the class. :(

Looks like valves are not nearly as useful as they should be. double :(

- -chris
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgPkzoACgkQ9CaO5/Lv0PB1fgCgutaBFqLgjPK2H16yWJ04YxAn
D3oAn0eLFzc8sHiWft3yKvtxFIbRLryI
=hwi6
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Zach Cox <zc...@gmail.com>.
Hi Chris - I'm not sure what this will accomplish:

public Request(Request wrapped)
{
~    this.wrapped = wrapped;
}

public whatever doWhatever(args)
{
~    super.doWhatever(args);
}

1) this.wrapped is never used so what's the point of saving it?
2) why override all of the methods just to call the super version of it?

I'm not sure I follow you...

Thanks,
Zach

-- 
View this message in context: http://www.nabble.com/Share-session-cookie-across-subdomains-tp16787390p16834778.html
Sent from the Tomcat - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Zach,

Zach Cox wrote:
| That's what I was afraid of - that Request class is huge (2600+ lines) and
| has lots of fields (50+) some of which are private.  So if I create a
| Request subclass that accepts another Request in the constructor,
there are
| 2 options:
| 1) initialize as many of the superclass's fields of the new instance as
| possible using values from the passed-in Request
| 2) wrap the passed-in Request & delegate method calls to it (100+ methods)

#2 is what you want to do. The constructor should look like this:

public Request(Request wrapped)
{
~    this.wrapped = wrapped;
}

That's it. Every other method should look like this:

public whatever doWhatever(args)
{
~    super.doWhatever(args);
}

| #2 is also not a good idea: my new Request subclass is not a true
| wrapper/adapter since I'm overriding one of the methods to get my desired
| behavior (configureSessionCookie).  That method is not called directly,
| rather is a protected method called by a public method so I have to
| pick-and-choose which methods get delegated and which don't, which results
| in state split across two Request objects which gets very confusing and is
| also very error-prone.

Just override the protected method. Protected methods were made to be
overridden. You are doing the exact same thing that is encouraged by the
Servlet API itself (see javax.servlet.http.HttpServletRequestWrapper).

| Overall, I'm going to call the Request API unsuitable for extension and in
| particular, I'm going to say overriding configureCookieSession in a
Request
| subclass is not a viable solution to the cross-subdomain session cookie
| problem.

Okay, happy coding.

| To make Request suitable for extension I can see 2 possible solutions:
| 1) Provide a way to specify the fully-qualified name of my Request
subclass
| in server.xml so Tomcat instantiates that instead of
| org.apache.catalina.connector.Request

You won't have to do this if you modify Request.java directly, which is
what you'll have to do in order to...

| 2) Provide a copy constructor in org.apache.catalina.connector.Request
that
| initializes its state based on the state of the passed-in Request

I'd be careful with this. You might not be able to get some of the
internal state this way.

If I were you, I'd start by submitting a bugzilla enhancement and patch
to achieve #1 above (a way to specify the class name for the request
class). Right now, each connector has "new Request()" hard-coded into
its constructor. This won't be an easy task, as you will have to either
modify the arguments to the connector's constructor (yuck), add a static
class field to specify the Request class to use (and break convention
within the Tomcat code), and in either case you'll have to modify every
connector implementation (or, at least the ones you are currently using)
/and/ any code that interacts with the connectors (because of the new
constructor args, new static field, whatever).

Trust me. Use a Valve even if you don't want to use a wrapper as I
suggest. Otherwise, you have to modify Tomcat code for each release,
which is horribly unmaintainable.

- -chris

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgN6rkACgkQ9CaO5/Lv0PCV6wCfV9Yh/wcd9aksuk1KXEUN1G9z
O2AAnj6c8dkK1x/APNSyCrmLYcPoe1Ef
=Px/z
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Zach Cox <zc...@gmail.com>.
That's what I was afraid of - that Request class is huge (2600+ lines) and
has lots of fields (50+) some of which are private.  So if I create a
Request subclass that accepts another Request in the constructor, there are
2 options:
1) initialize as many of the superclass's fields of the new instance as
possible using values from the passed-in Request
2) wrap the passed-in Request & delegate method calls to it (100+ methods)

#1 is very error-prone, as many of the set methods in Request do things
other than just setting the instance variable to a new value and in a future
release when Request inevitably changes it will be broken.

#2 is also not a good idea: my new Request subclass is not a true
wrapper/adapter since I'm overriding one of the methods to get my desired
behavior (configureSessionCookie).  That method is not called directly,
rather is a protected method called by a public method so I have to
pick-and-choose which methods get delegated and which don't, which results
in state split across two Request objects which gets very confusing and is
also very error-prone.

Overall, I'm going to call the Request API unsuitable for extension and in
particular, I'm going to say overriding configureCookieSession in a Request
subclass is not a viable solution to the cross-subdomain session cookie
problem.  

To make Request suitable for extension I can see 2 possible solutions:
1) Provide a way to specify the fully-qualified name of my Request subclass
in server.xml so Tomcat instantiates that instead of
org.apache.catalina.connector.Request
2) Provide a copy constructor in org.apache.catalina.connector.Request that
initializes its state based on the state of the passed-in Request

If I'm missing something, please correct me.

Thanks,
Zach




Christopher Schultz-2 wrote:
> 
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> Zach,
> 
> Zach Cox wrote:
> | Hi Chris - I thought about doing that, but that existing Request
> | object could have state that my newly created Request will not have -
> | Request does not have a copy constructor and does not wrap another
> | Request object.
> 
> That's why you have to write your own Request class that extends the
> existing Request class, includes a constructor that takes the "wrapped"
> Request, and delegates nearly all operations to this wrapped object.
> 
> | Is it OK to just create a fresh, new, un-initialized Request object &
> | pass it to the next valve in the chain?
> 
> No, you have to do what I have described above.
> 
> - -chris
> 
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (MingW32)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
> 
> iEYEARECAAYFAkgNCmQACgkQ9CaO5/Lv0PARGACgrCxjxUQfVFqxfjilm/wFeQuk
> RkIAoJEM7Jp45BpuBTuR1+M/N2x8mHIh
> =Dork
> -----END PGP SIGNATURE-----
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/Share-session-cookie-across-subdomains-tp16787390p16823816.html
Sent from the Tomcat - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Zach,

Zach Cox wrote:
| Hi Chris - I thought about doing that, but that existing Request
| object could have state that my newly created Request will not have -
| Request does not have a copy constructor and does not wrap another
| Request object.

That's why you have to write your own Request class that extends the
existing Request class, includes a constructor that takes the "wrapped"
Request, and delegates nearly all operations to this wrapped object.

| Is it OK to just create a fresh, new, un-initialized Request object &
| pass it to the next valve in the chain?

No, you have to do what I have described above.

- -chris

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgNCmQACgkQ9CaO5/Lv0PARGACgrCxjxUQfVFqxfjilm/wFeQuk
RkIAoJEM7Jp45BpuBTuR1+M/N2x8mHIh
=Dork
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Zach Cox <zc...@gmail.com>.
Hi Chris - I thought about doing that, but that existing Request object could
have state that my newly created Request will not have - Request does not
have a copy constructor and does not wrap another Request object.  Is it OK
to just create a fresh, new, un-initialized Request object & pass it to the
next valve in the chain?

Thanks,
Zach



Christopher Schultz-2 wrote:
> 
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
> 
> Zach,
> 
> Zach Cox wrote:
> | Note that this entire issue would be made much more simple if there was
> an
> | easy way to override the
> | org.apache.catalina.connector.Request.configureSessionCookie method.
> I have
> | not found an easy way to do that.
> 
> Have your Valve wrap the Request object with your own class that
> overrides that method. Then just move your existing cookie
> checking/replacing code into the configureSessionCookie method.
> 
> - -chris
> 
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (MingW32)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
> 
> iEYEARECAAYFAkgMmnsACgkQ9CaO5/Lv0PA/WACeK9Rcw1ltSqoVC/pFXUZFtob3
> JYUAnRxnQV+FEtkxqruWMfA0U2p/Qg9O
> =ScpV
> -----END PGP SIGNATURE-----
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/Share-session-cookie-across-subdomains-tp16787390p16810830.html
Sent from the Tomcat - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


Re: Share session cookie across subdomains

Posted by Christopher Schultz <ch...@christopherschultz.net>.
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Zach,

Zach Cox wrote:
| Note that this entire issue would be made much more simple if there was an
| easy way to override the
| org.apache.catalina.connector.Request.configureSessionCookie method.
I have
| not found an easy way to do that.

Have your Valve wrap the Request object with your own class that
overrides that method. Then just move your existing cookie
checking/replacing code into the configureSessionCookie method.

- -chris

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (MingW32)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkgMmnsACgkQ9CaO5/Lv0PA/WACeK9Rcw1ltSqoVC/pFXUZFtob3
JYUAnRxnQV+FEtkxqruWMfA0U2p/Qg9O
=ScpV
-----END PGP SIGNATURE-----

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


RE: Share session cookie across subdomains

Posted by Zach Cox <zc...@gmail.com>.
That's a good point Matthias - in my case sessions are always created so it's
acceptable.  Can you think of a good way to only replace the session cookie
when it would be naturally created by the app for those cases where always
creating it is not acceptable?

Note that this entire issue would be made much more simple if there was an
easy way to override the
org.apache.catalina.connector.Request.configureSessionCookie method.  I have
not found an easy way to do that.

Thanks,
Zach



Reich, Matthias wrote:
> 
> With your implementation, the valve will have the effect that a session
> is created upon first access to a server, 
> not only when required by the application.
> (see also discussion on
> http://www.nabble.com/Cookie-less-session-tracking---whats-are-the-downs
> ides-tp16738472p16738472.html )
> 
> Thus, the valve is only suitable if this behavior is acceptable.
> (e.g. if you can be sure that your site is not accessed by search engine
> robots, if you have a rather short session timeout and can live with the
> overhead of creating many unnecessary session objects, ...)
> 
> -- Matthias
> 
> -----Original Message-----
> From: Zach Cox [mailto:zcox522@gmail.com] 
> Sent: Saturday, April 19, 2008 10:26 PM
> To: users@tomcat.apache.org
> Subject: Share session cookie across subdomains
> 
> I'm working on a site that has a single web app in Tomcat that handles
> lots of subdomains via wildcard DNS:
>  - site.com
>  - www.site.com
>  - sub1.site.com
>  - sub2.site.com
>  - etc.
> 
> Basically, the site content gets customized based on the subdomain in
> the request, if any.  However I ran into a problem where Tomcat would
> use separate JSESSIONID session cookies on each different domain which
> causes problems as the user would have multiple sessions if they
> switched subdomains.
> 
> I found an examle Valve at http://www.esus.be/blog/?p=3 but it
> ultimately did not result in a cross-subdomain session cookie (at
> least in the Tomcat 6.0.14 I'm using).  I'm posting the Valve that did
> actually seem to solve the problem for me below in hopes that it will
> help someone else with the same problem in the future.
> 
> Basically you end up with a single session cookie with its domain set
> to .site.com so the client will send it back in the request for any
> subdomain.
> 
> Usage:
>  - compile CrossSubdomainSessionValve & put it in a .jar file
>  - put that .jar file in $CATALINA_HOME/lib directory
>  - include a <Valve
> className="org.three3s.valves.CrossSubdomainSessionValve"/> in
> $CATALINA_HOME/conf/server.xml
> 
> If you see/experience any problems please let me know!
> 
> Thanks,
> Zach
> 
> 
> package org.three3s.valves;
> 
> import java.io.*;
> 
> import javax.servlet.*;
> import javax.servlet.http.*;
> 
> import org.apache.catalina.*;
> import org.apache.catalina.connector.*;
> import org.apache.catalina.valves.*;
> import org.apache.tomcat.util.buf.*;
> import org.apache.tomcat.util.http.*;
> 
> /** <p>Replaces the domain of the session cookie generated by Tomcat
> with a domain that allows that
>  * session cookie to be shared across subdomains.  This valve digs
> down into the response headers
>  * and replaces the Set-Cookie header for the session cookie, instead
> of futilely trying to
>  * modify an existing Cookie object like the example at
> http://www.esus.be/blog/?p=3.  That
>  * approach does not work (at least as of Tomcat 6.0.14) because the
>  * <code>org.apache.catalina.connector.Response.addCookieInternal</code>
> method renders the
>  * cookie into the Set-Cookie response header immediately, making any
> subsequent modifying calls
>  * on the Cookie object ultimately pointless.</p>
>  *
>  * <p>This results in a single, cross-subdomain session cookie on the
> client that allows the
>  * session to be shared across all subdomains.  However, see the
> {@link getCookieDomain(Request)}
>  * method for limits on the subdomains.</p>
>  *
>  * <p>Note though, that this approach will fail if the response has
> already been committed.  Thus,
>  * this valve forces Tomcat to generate the session cookie and then
> replaces it before invoking
>  * the next valve in the chain.  Hopefully this is early enough in the
> valve-processing chain
>  * that the response will not have already been committed.  You are
> advised to define this
>  * valve as early as possible in server.xml to ensure that the
> response has not already been
>  * committed when this valve is invoked.</p>
>  *
>  * <p>We recommend that you define this valve in server.xml
> immediately after the Catalina Engine
>  * as follows:
>  * <pre>
>  * &lt;Engine name="Catalina"...&gt;
>  *     &lt;Valve
> className="org.three3s.valves.CrossSubdomainSessionValve"/&gt;
>  * </pre>
>  * </p>
>  */
> public class CrossSubdomainSessionValve extends ValveBase
> {
>     public CrossSubdomainSessionValve()
>     {
>         super();
>         info = "org.three3s.valves.CrossSubdomainSessionValve/1.0";
>     }
> 
>     @Override
>     public void invoke(Request request, Response response) throws
> IOException, ServletException
>     {
>         //this will cause Request.doGetSession to create the session
> cookie if necessary
>         request.getSession(true);
> 
>         //replace any Tomcat-generated session cookies with our own
>         Cookie[] cookies = response.getCookies();
>         if (cookies != null)
>         {
>             for (int i = 0; i < cookies.length; i++)
>             {
>                 Cookie cookie = cookies[i];
>                 containerLog.debug("CrossSubdomainSessionValve: Cookie
> name is " + cookie.getName());
>                 if
> (Globals.SESSION_COOKIE_NAME.equals(cookie.getName()))
>                     replaceCookie(request, response, cookie);
>             }
>         }
> 
>         //process the next valve
>         getNext().invoke(request, response);
>     }
> 
>     /** Replaces the value of the response header used to set the
> specified cookie to a value
>      * with the cookie's domain set to the value returned by
> <code>getCookieDomain(request)</code>
>      *
>      * @param request
>      * @param response
>      * @param cookie cookie to be replaced.
>      */
>     @SuppressWarnings("unchecked")
>     protected void replaceCookie(Request request, Response response,
> Cookie cookie)
>     {
>         //copy the existing session cookie, but use a different domain
>         Cookie newCookie = new Cookie(cookie.getName(),
> cookie.getValue());
>         if (cookie.getPath() != null)
>             newCookie.setPath(cookie.getPath());
>         newCookie.setDomain(getCookieDomain(request));
>         newCookie.setMaxAge(cookie.getMaxAge());
>         newCookie.setVersion(cookie.getVersion());
>         if (cookie.getComment() != null)
>             newCookie.setComment(cookie.getComment());
>         newCookie.setSecure(cookie.getSecure());
> 
>         //if the response has already been committed, our replacement
> strategy will have no effect
>         if (response.isCommitted())
>             containerLog.error("CrossSubdomainSessionValve: response
> was already committed!");
> 
>         //find the Set-Cookie header for the existing cookie and
> replace its value with new cookie
>         MimeHeaders headers =
> response.getCoyoteResponse().getMimeHeaders();
>         for (int i = 0, size = headers.size(); i < size; i++)
>         {
>             if (headers.getName(i).equals("Set-Cookie"))
>             {
>                 MessageBytes value = headers.getValue(i);
>                 if (value.indexOf(cookie.getName()) >= 0)
>                 {
>                     StringBuffer buffer = new StringBuffer();
>                     ServerCookie.appendCookieValue(buffer,
> newCookie.getVersion(), newCookie
>                             .getName(), newCookie.getValue(),
> newCookie.getPath(), newCookie
>                             .getDomain(), newCookie.getComment(),
> newCookie.getMaxAge(), newCookie
>                             .getSecure());
>                     containerLog.debug("CrossSubdomainSessionValve:
> old Set-Cookie value: "
>                             + value.toString());
>                     containerLog.debug("CrossSubdomainSessionValve:
> new Set-Cookie value: " + buffer);
>                     value.setString(buffer.toString());
>                 }
>             }
>         }
>     }
> 
>     /** Returns the last two parts of the specified request's server
> name preceded by a dot.
>      * Using this as the session cookie's domain allows the session to
> be shared across subdomains.
>      * Note that this implies the session can only be used with
> domains consisting of two or
>      * three parts, according to the domain-matching rules specified
> in RFC 2109 and RFC 2965.
>      *
>      * <p>Examples:</p>
>      * <ul>
>      * <li>foo.com => .foo.com</li>
>      * <li>www.foo.com => .foo.com</li>
>      * <li>bar.foo.com => .foo.com</li>
>      * <li>abc.bar.foo.com => .foo.com - this means cookie won't work
> on abc.bar.foo.com!</li>
>      * </ul>
>      *
>      * @param request provides the server name used to create cookie
> domain.
>      * @return the last two parts of the specified request's server
> name preceded by a dot.
>      */
>     protected String getCookieDomain(Request request)
>     {
>         String cookieDomain = request.getServerName();
>         String[] parts = cookieDomain.split("\\.");
>         if (parts.length >= 2)
>             cookieDomain = parts[parts.length - 2] + "." +
> parts[parts.length - 1];
>         return "." + cookieDomain;
>     }
> 
>     public String toString()
>     {
>         return ("CrossSubdomainSessionValve[container=" +
> container.getName() + ']');
>     }
> }
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 
> ---------------------------------------------------------------------
> To start a new topic, e-mail: users@tomcat.apache.org
> To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: users-help@tomcat.apache.org
> 
> 
> 

-- 
View this message in context: http://www.nabble.com/Share-session-cookie-across-subdomains-tp16787390p16807270.html
Sent from the Tomcat - User mailing list archive at Nabble.com.


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


RE: Share session cookie across subdomains

Posted by "Reich, Matthias" <ma...@siemens.com>.
With your implementation, the valve will have the effect that a session
is created upon first access to a server, 
not only when required by the application.
(see also discussion on
http://www.nabble.com/Cookie-less-session-tracking---whats-are-the-downs
ides-tp16738472p16738472.html )

Thus, the valve is only suitable if this behavior is acceptable.
(e.g. if you can be sure that your site is not accessed by search engine
robots, if you have a rather short session timeout and can live with the
overhead of creating many unnecessary session objects, ...)

-- Matthias

-----Original Message-----
From: Zach Cox [mailto:zcox522@gmail.com] 
Sent: Saturday, April 19, 2008 10:26 PM
To: users@tomcat.apache.org
Subject: Share session cookie across subdomains

I'm working on a site that has a single web app in Tomcat that handles
lots of subdomains via wildcard DNS:
 - site.com
 - www.site.com
 - sub1.site.com
 - sub2.site.com
 - etc.

Basically, the site content gets customized based on the subdomain in
the request, if any.  However I ran into a problem where Tomcat would
use separate JSESSIONID session cookies on each different domain which
causes problems as the user would have multiple sessions if they
switched subdomains.

I found an examle Valve at http://www.esus.be/blog/?p=3 but it
ultimately did not result in a cross-subdomain session cookie (at
least in the Tomcat 6.0.14 I'm using).  I'm posting the Valve that did
actually seem to solve the problem for me below in hopes that it will
help someone else with the same problem in the future.

Basically you end up with a single session cookie with its domain set
to .site.com so the client will send it back in the request for any
subdomain.

Usage:
 - compile CrossSubdomainSessionValve & put it in a .jar file
 - put that .jar file in $CATALINA_HOME/lib directory
 - include a <Valve
className="org.three3s.valves.CrossSubdomainSessionValve"/> in
$CATALINA_HOME/conf/server.xml

If you see/experience any problems please let me know!

Thanks,
Zach


package org.three3s.valves;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.catalina.*;
import org.apache.catalina.connector.*;
import org.apache.catalina.valves.*;
import org.apache.tomcat.util.buf.*;
import org.apache.tomcat.util.http.*;

/** <p>Replaces the domain of the session cookie generated by Tomcat
with a domain that allows that
 * session cookie to be shared across subdomains.  This valve digs
down into the response headers
 * and replaces the Set-Cookie header for the session cookie, instead
of futilely trying to
 * modify an existing Cookie object like the example at
http://www.esus.be/blog/?p=3.  That
 * approach does not work (at least as of Tomcat 6.0.14) because the
 * <code>org.apache.catalina.connector.Response.addCookieInternal</code>
method renders the
 * cookie into the Set-Cookie response header immediately, making any
subsequent modifying calls
 * on the Cookie object ultimately pointless.</p>
 *
 * <p>This results in a single, cross-subdomain session cookie on the
client that allows the
 * session to be shared across all subdomains.  However, see the
{@link getCookieDomain(Request)}
 * method for limits on the subdomains.</p>
 *
 * <p>Note though, that this approach will fail if the response has
already been committed.  Thus,
 * this valve forces Tomcat to generate the session cookie and then
replaces it before invoking
 * the next valve in the chain.  Hopefully this is early enough in the
valve-processing chain
 * that the response will not have already been committed.  You are
advised to define this
 * valve as early as possible in server.xml to ensure that the
response has not already been
 * committed when this valve is invoked.</p>
 *
 * <p>We recommend that you define this valve in server.xml
immediately after the Catalina Engine
 * as follows:
 * <pre>
 * &lt;Engine name="Catalina"...&gt;
 *     &lt;Valve
className="org.three3s.valves.CrossSubdomainSessionValve"/&gt;
 * </pre>
 * </p>
 */
public class CrossSubdomainSessionValve extends ValveBase
{
    public CrossSubdomainSessionValve()
    {
        super();
        info = "org.three3s.valves.CrossSubdomainSessionValve/1.0";
    }

    @Override
    public void invoke(Request request, Response response) throws
IOException, ServletException
    {
        //this will cause Request.doGetSession to create the session
cookie if necessary
        request.getSession(true);

        //replace any Tomcat-generated session cookies with our own
        Cookie[] cookies = response.getCookies();
        if (cookies != null)
        {
            for (int i = 0; i < cookies.length; i++)
            {
                Cookie cookie = cookies[i];
                containerLog.debug("CrossSubdomainSessionValve: Cookie
name is " + cookie.getName());
                if
(Globals.SESSION_COOKIE_NAME.equals(cookie.getName()))
                    replaceCookie(request, response, cookie);
            }
        }

        //process the next valve
        getNext().invoke(request, response);
    }

    /** Replaces the value of the response header used to set the
specified cookie to a value
     * with the cookie's domain set to the value returned by
<code>getCookieDomain(request)</code>
     *
     * @param request
     * @param response
     * @param cookie cookie to be replaced.
     */
    @SuppressWarnings("unchecked")
    protected void replaceCookie(Request request, Response response,
Cookie cookie)
    {
        //copy the existing session cookie, but use a different domain
        Cookie newCookie = new Cookie(cookie.getName(),
cookie.getValue());
        if (cookie.getPath() != null)
            newCookie.setPath(cookie.getPath());
        newCookie.setDomain(getCookieDomain(request));
        newCookie.setMaxAge(cookie.getMaxAge());
        newCookie.setVersion(cookie.getVersion());
        if (cookie.getComment() != null)
            newCookie.setComment(cookie.getComment());
        newCookie.setSecure(cookie.getSecure());

        //if the response has already been committed, our replacement
strategy will have no effect
        if (response.isCommitted())
            containerLog.error("CrossSubdomainSessionValve: response
was already committed!");

        //find the Set-Cookie header for the existing cookie and
replace its value with new cookie
        MimeHeaders headers =
response.getCoyoteResponse().getMimeHeaders();
        for (int i = 0, size = headers.size(); i < size; i++)
        {
            if (headers.getName(i).equals("Set-Cookie"))
            {
                MessageBytes value = headers.getValue(i);
                if (value.indexOf(cookie.getName()) >= 0)
                {
                    StringBuffer buffer = new StringBuffer();
                    ServerCookie.appendCookieValue(buffer,
newCookie.getVersion(), newCookie
                            .getName(), newCookie.getValue(),
newCookie.getPath(), newCookie
                            .getDomain(), newCookie.getComment(),
newCookie.getMaxAge(), newCookie
                            .getSecure());
                    containerLog.debug("CrossSubdomainSessionValve:
old Set-Cookie value: "
                            + value.toString());
                    containerLog.debug("CrossSubdomainSessionValve:
new Set-Cookie value: " + buffer);
                    value.setString(buffer.toString());
                }
            }
        }
    }

    /** Returns the last two parts of the specified request's server
name preceded by a dot.
     * Using this as the session cookie's domain allows the session to
be shared across subdomains.
     * Note that this implies the session can only be used with
domains consisting of two or
     * three parts, according to the domain-matching rules specified
in RFC 2109 and RFC 2965.
     *
     * <p>Examples:</p>
     * <ul>
     * <li>foo.com => .foo.com</li>
     * <li>www.foo.com => .foo.com</li>
     * <li>bar.foo.com => .foo.com</li>
     * <li>abc.bar.foo.com => .foo.com - this means cookie won't work
on abc.bar.foo.com!</li>
     * </ul>
     *
     * @param request provides the server name used to create cookie
domain.
     * @return the last two parts of the specified request's server
name preceded by a dot.
     */
    protected String getCookieDomain(Request request)
    {
        String cookieDomain = request.getServerName();
        String[] parts = cookieDomain.split("\\.");
        if (parts.length >= 2)
            cookieDomain = parts[parts.length - 2] + "." +
parts[parts.length - 1];
        return "." + cookieDomain;
    }

    public String toString()
    {
        return ("CrossSubdomainSessionValve[container=" +
container.getName() + ']');
    }
}

---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org


---------------------------------------------------------------------
To start a new topic, e-mail: users@tomcat.apache.org
To unsubscribe, e-mail: users-unsubscribe@tomcat.apache.org
For additional commands, e-mail: users-help@tomcat.apache.org