I was just wondering if anyone has ever tried sharing a jsecurity subject object between 2 separate webapps sitting on the same web container? I know that this is a more of a servlet programming question (more suited for a more appropriate forum), but I was wondering if jsecurity provides any mechanisms for this to happen?
I apologize that this is such a generic, open-ended question, but thought I'd give it a try anyway.
Thanks in advance,
Todd Kofford
tkofford@ku.edu
Hi Todd, Since they are
Hi Todd,
Since they are separate webapps/contexts, they are viewed as separate applications. You could have both applications use the same JSecurity Realm objects, in which case they'd point to the same datasource(s) and function the same way.
Another approach is to use JSecurity's enterprise sessions so session state could be shared across both applications. When you log in to one webapp, you would be logged in to the other web app as well by sessionId sharing.
Yet another way to do this is to have JSecurity use a distributed caching mechanism (e.g. TerraCotta), and TerraCotta actually clusters the Subject's Session across the different applications. Because Subject instances are created based on Sessions (i.e. a Session stores principals for a logged in user, and the Subject instance is created in response to these principals), if you clustered the Sessions across applications, you'd immediately have instant access across both apps.
Is this the kind of thing you're talking about?
- Les
JSecurity's enterprise sessions
I'd really like to dig into JSecurity's enterprise sessions. I need to have two separate webapps share session state information. Eventually, these webapps will be residing on several tomcat instances in a cluster, and I also need to know if enterprise sessions will work for that environment too.
Are there any examples on how to implement this feature, so I can do some experimenting? I've been reading through the API docs, but a simple example would really help out.
Thanks in advance,
Todd Kofford
tkofford@ku.edu
Re: JSecurity's enterprise sessions
Ok, here's the last approach, and probably the easiest (not not the most performant):
Implement the SessionDAO interface to talk to a relational database or Berkley DB to store the sessions. If both applications used that same SessionDAO implementation to point to the same data source at runtime, then they automatically share the session state. 'Poor man's clustering...' :)
This would work, but the scalability of it is entirely based on the performance of that data source. When clustering sessions, using a common database to store session data is the least performant because of socket overhead and marshalling/unmarshalling of the data. TerraCotta or Coherence are orders of magnitude faster at runtime due to memory locality and their highly efficient serialization approaches.
I'm just throwing out the final option that I know of for completeness :)
Cheers,
Les
Re: JSecurity's enterprise sessions - Options
Hi Les,
"We're not worthy ... we're not worthy ..."
Seriously, thanks a million! It's almost impossible to get the detailed help like this on most forums. It is most certainly appreciated.
I am going to spend today, evaluating our needs and the solutions that you provided. I'm sure that I'll be able to figure something out that will work for us.
Also, I'd be happy to contribute anything back to the project. Since we're using struts 2 and not spring for our framework, I could probably post a comment or two on how we implemented jsecurity along with some code samples or maybe a simple struts 2 webapp.
Thanks again,
Todd Kofford
tkofford@ku.edu
Re: Re: JSecurity's enterprise sessions - Options
Sounds good Todd. I look forward to it!
Cheers,
Les
Re: JSecurity's enterprise sessions
Ah, I almost forgot.
The other way to do this, without clustering is to have Application B 'point' to Application A's SessionManager, typically by a remoting proxy. For example, in Spring, you can configure it such that Application B's SessionManager object is really just a proxy back to the remotely-exposed SessionManager in Application A. Then for each session invocation in Application B, you would have a remote method invocation back to application A which would then do the real work.
This is just one approach. The TerraCotta suggestion is more scalable - i.e. the more applications that need to share session state, the better terracotta will be able to handle state transfer and you won't need to do remote method invocations, enabling better performance.
TerraCotta (or commercial solutions like Coherence) is the way to go in 'enterprise' environments, or when you have scalability in mind. If its for two apps and they're on the same gigabit backbone and it won't grow beyond that, then the remoting approach is probably just fine. But terracotta is about as easy to set up as the remoting solution, so the architect in me says that it is probably a better solution...
Re: JSecurity's enterprise sessions
Hi Todd!
I don't have any code examples, but the principle is straightforward but might be a tad time consuming. This is how I recommend you do it:
The JSecurity default
SessionManagerimplementation delegates to aSessionDAO. This defaultSessionDAOis aMemorySessionDAO, but its name is kind of misleading. It really delegates its storage ofSessions to aCacheinstance (assuming that most cache instances are memory based, hence the name I guess - we should think about renaming that, but that's beside the point).This is key because if the
Cacheis clustered, then theSessioninfrastructure is automatically clustered. If any application joins that cache cluster, then they automatically have access to those sessions.So what is our preferred way to do this?
The default Cache implementation in JSecurity is EhCache. My preferred open source clustering solution is TerraCotta.
TerraCotta has documentation on how to integrate it pretty easily with EhCache. So, Ehcache sort of 'sits' on top of TerraCotta in a way. Here is more information:
So, the flow works like this:
When a session is created by an end-user, the
SessionManagercallessessionDAO.create(Session). The MemorySessionDAO callscache.put(sessionId, session);. The defaultCacheimplementation callsanEhCacheInstance.put(sessionId, session);. If you have TerraCotta enabled (see the above 2nd link), then that session object will be clustered.Also note that EhCache now has built-in clustering, and may be sufficient enough to not use TerraCotta. I personally haven't used it before, but it might be worth trying out. I do know that EhCache's clustering solution is somewhat new and that TerraCotta is a commercial-grade clustering solution. EhCache might have support now for using JGroups to cluster, which is very mature, so that might be good enough. I encourage you to try both out (if you have the time), and let us know!
Also, please, please, please feel free to contribute back anything you find of use to the project when setting this up. What you do will no doubt be useful for many others! You also might want to join our email list (jsecurity-dev@incubator.jsecurity.org) for continued support while going through the process. Many people are willing to help.
Best regards,
Les
Trouble Sharing Subject between webapps
Well, I seem to have run into a bit of a snag and cannot seem to get my two webapps to retrieve the same subject. I also had to make the following change in my AppSecurityFilter that I've implemented (extends JSecurityFilter) so that it uses jsecurity sessions rather than http servlet sessions:
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
dwsm.setRealm(realm);
dwsm.setSessionMode(JSECURITY_SESSION_MODE);
I've also, setup the same filter for my second webapp, and I'm using ehcache.
I'm probably missing something obvious, but shouldn't I be able to do the following in both webapps and get the same subject/currentUser object since it is in cache?
Subject currentUser = SecurityUtils.getSubject();
Thanks,
Todd Kofford
tkofford@ku.edu
Trouble Sharing Subject between webapps
Hi Todd,
I'm assuming your different web apps have different paths in the server?
I.e. /app1/blah.jsp and /app2/blah.jsp?
If so, that means the cookie being set for app1 is not being accessed by app2. Each app will write its own session cookie because cookies are unique based on the context path by default. In a multi-app scenario, they must access the same cookie so they can point to the same session in the distributed cache.
You can do that by configuring the following:
WebRememberMeManager wrmm = new org.jsecurity.web.WebRememberMeManager();wrmm.setCookiePath("/");
securityManager.setRememberMeManager(wrmm);
This makes the session cookie be set at the root of the server instead at the context path. Then, both apps would see the same cookie.
That should do it. Lemme know how it works!
Cheers,
Les
Still Missing Something?
Hi Les,
Yes, both webapps have different paths in the server. I setup the jsecurity filter for each individual webapp, but noticed that I'm now getting two different instances of the security manager when the filter is called for each webapp. Do I need to define this filter at the root tomcat level (i.e., <tomcathome>conf/web.xml) instead of under each webapps context path web.xml (i.e., <tomcathome>webapps/webapp1/WEB-INF/web.xml & <tomcathome>webapps/webapp2/WEB-INF/web.xml)? I usually try not to define anything there, but if that is the correct way to do it then that's OK.
Thanks,
Todd
Re: Still Missing Something?
Hi Todd,
You definitely don't want to mess with tomcat's global configuration if you can help it. :)
And two different SecurityManager instances is perfectly fine - nothing wrong with that. What matters is that the SecurityManager (which has a wrapped SessionManager) uses a SessionDAO that has a Cache instance pointing to the same cache cluster.
In fact, each application _should_ have their own SecurityManager, session manager, SessionDAO, etc. Its just the data source that the different SessionDAOs point to (in this case a clustered cache) would be the same.
Does that make sense?
Les
Config to ensure a single cache data source?
I understand about the separate security mgrs, etc. and that's good to know, but how do I configure both SessionDAOs to use the same data source?
Currently, I'm not doing any clustering, but rather just trying to get this working on a single machine. I have done nothing with creating a custom ehcache.xml at this point. Do I need to create a custom ehcache.xml file or do I need to set the shared data source in the filter class (i.e., ehcache configuration or jsecurity filter setup)?
It appears that each webapp is also creating it's own instance of a CacheManager rather than using a singleton. I'm assuming that this is OK too. I just don't understand the code well enough yet to know where I setup the single cache data source.
Thanks,
Todd
Config to ensure a single cache data source?
Yep, you're on the right track - this will all boil down to a custom ehcache config if you want to use Ehcache's native clustering, or to TerraCotta configuration if you use TerraCotta clustering instead (I think I posted the link to TerraCotta's website explaining how to set up both together).
The default JSecurity set up is this object graph:
SecurityManager --> SessionManager --> SessionDAO --> Cache
The Cache instance is an EhCache implementation obtained by using the SecurityManager's built-in CacheManager, which happens to be an EhCacheManager by default.
So, if you did this:
EhCacheManager cacheManager = new org.jsecurity.cache.ehcache.EhCacheManager();
cacheManager.setCacheManagerConfigFile( "classpath:ehcache.xml" );
cacheManager.init();
securityManager.setCacheManager( cacheManager );
//other SM injected dependencies here
...
securityManager.init();
Then JSecurity will use an EhCache CacheManager with your custom ehcache configuration. If that custom cache configuration enables clustering, and you use that same config in both your applications, then they'll share the session cache.
Also, take a look at JSecurity's failsafe ehcache config.
If you have a custom ehcache config, the most important thing to do is to ensure that you have defined a jsecurity-activeSessionCache region (you'll see it in our config). Just re-define that one in your file to make sure it is available when your ehcache CacheManager starts up as well.
I think that is all that is required for a custom ehcache config setup for network based clustering.
2 webapps & 2 caches ???
I've configured the cluster by creating a custom ehcache.xml file for each webapp (same file for both). However, I'm seem to be getting 2 separate caches, one for each webapp. In my ../tomcat/temp/jsecurity-ehcache directory I have the following entries:
jsecurity-ehcache
I don't know if that tells you anything or not.
Here is my part of my filter code:
public SecurityManager getSecurityManager()
{
if (this.securityManager == null)
{
DefaultRealm realm = new DefaultRealm();
// DON'T init() it yet, see below.
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
EhCacheManager cacheManager = new org.jsecurity.cache.ehcache.EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
cacheManager.init();
dwsm.setCacheManager(cacheManager);
//other SM injected dependencies here
dwsm.setRealm(realm);
dwsm.setSessionMode(JSECURITY_SESSION_MODE);
WebRememberMeManager wrmm = new WebRememberMeManager();
wrmm.setCookiePath("/");
dwsm.setRememberMeManager(wrmm);
// This next call will automatically create an EhCacheManager and
// inject it into 'myRealm'.
dwsm.init();
// Now that the CacheManager has been injected into the realm,
// this next init() call will acquire the cache used by that realm
// for Account objects:
realm.init();
this.securityManager = dwsm;
}
return this.securityManager;
}
I'm sure that I'm missing a small step somewhere.
Any suggestions?
Thanks,
Todd
Ah yes - really close. But
Ah yes - really close. But my fault.
Try this alteration to the config:
...
EhCacheManager cacheManager = new org.jsecurity.cache.ehcache.EhCacheManager();
cacheManager.setCacheManager( net.sf.ehcache.CacheManager.getInstance() );
dwsm.setCacheManager(cacheManager);
...
And also make sure that ehcache.jar is not in each web app's WEB-INF/lib folder. You want it to be in $CATALINA_HOME/lib to ensure that the CacheManager.getInstance() call across both applications is static across the VM - not unique to each web app's class loader as would be the case in WEB-INF/lib. Also ensure that your custom ehcache.xml is at the root of the classpath - not your web-app's classloader (i.e. not in WEB-INF/classes). You can do that by setting the CLASSPATH environment variable:
For example, if you wanted to put your custom ehcache.xml in $CATALINA_HOME/lib for lack of a better location, you would set CLASSPATH=$CATLINA_HOME/lib.
Again, I'm just recommending that you do this $CATALINA_HOME/lib technique (both the ehcache.jar and custom ehcache.xml) to ensure there is one ehcache CacheManager instance accessible to any webapp because of the way web classloader delegation works. The classloader stuff is not in effect if you use ehcache's native clustering. But one step at a time ;)
Let me know how it goes!
- Les
P.S. You'll be pleased to know that the next release will make all of this injection/init() business much simpler ;)
I thought I had mentioned
I thought I had mentioned the CacheManager.getInstance() technique already - I thought I was getting forgetful ;)
I basically outlined the same thing in the "Re: Config to ensure a single cache data source?" thread just below this one.
I'll try the singleton approach
Hi Les,
I'll try the singleton approach right now and let you know how it turns out ASAP.
I hadn't tried the singleton method up to this point, but the clustering will not happen until we get to a production platform anyway, so I still have some time to get all the details worked out.
Thanks,
Todd
CacheManager is Same - Subject is Different?
OK, after a long debugging session, I have the CacheManager.manager pointing to the same object for both webapps. However, when I call SecurityUtils.getSubject() in each webapp to get the current user, the webapps return different values.
For example, in webapp1, I call SecurityUtils.getSubject() to obtain a subject which I then use to authenticate and login with (subject.isAuthenticated() == true). However, in webapp2, when I call SecurityUtils.getSubject(), I get a totally different subject object and subject.isAuthenticated() == false. I also noticed that in webapp1, the subject that I get always has a non-null principal collection, while the subject from webapp2 principals is null.
Below is the code from my AppSecurityFilter class (extends JSecurityFilter) for both webapps.
public SecurityManager getSecurityManager()
{
if (this.securityManager == null)
{
DefaultRealm realm = new DefaultRealm();
// DON'T init() it yet, see below.
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
cacheManager.setCacheManager( CacheManager.getInstance() );
cacheManager.init();
dwsm.setCacheManager(cacheManager);
//other SM injected dependencies here
dwsm.setRealm(realm);
dwsm.setSessionMode(JSECURITY_SESSION_MODE);
WebRememberMeManager wrmm = new WebRememberMeManager();
wrmm.setCookiePath("/");
dwsm.setRememberMeManager(wrmm);
// This next call will automatically create an EhCacheManager and
// inject it into 'myRealm'.
dwsm.init();
// Now that the CacheManager has been injected into the realm,
// this next init() call will acquire the cache used by that realm
// for Account objects:
realm.init();
this.securityManager = dwsm;
}
return this.securityManager;
}
Could this have something to do with session handling? My 1st webapp is a struts2 webapp that I have control over session creation, but my second webapp is a JSF webapp where I have no control over the session.
Any suggestions would be appreciated.
Thanks,
Todd
Session IDs?
Digging through the code, I discovered that the DefaultWebSessionManager is returning different sessionIDs via retrieveSessionId() during the DefaultWebSecurityManager.createSubject() call for each webapp.
This would probably explain why I get different subject objects from each webapp using the SecurityUtils.getSubject() method. However I still don't know where to "fix" it.
Perhaps there's a better way to share a Suject object between two webapps, rather than using the SecurityUtils.getSubject() method?
--Todd
Re: Session IDs?
Hi Todd,
Yep, this is the crux of the issue - the sessionId referenced from one webapp must be the same for the other webapp if you want to share the Subject.
SecurityUtils.getSubject()is still the appropriate way to acquire the Subject though.So, if the retrieveSessionId() is returning different or non-existent sessionIds, then it must mean that the sessionId cookie can't be read correctly. I suspected this was because of a domain/path issue, where the cookies were path-specific, but if you've set both cookie paths to "/", then it _should_ be accessible to both. Are the the two webapps on a different host? If you use firefox and can see all cookies, can you tell me all the information about the two cookies being set? (host name, path, etc - you can use fake names for these of course).
I don't understand why a request to web-app A can't see the same cookie as a request to web-app B. I've done this technique on my own apps before, so I know its possible...
Finally a last resort is to append the sessionID to the request URL that sends the user to the other application (e.g. on web app A's page, have a link to http://webappB/home.jsp?JSESSIONID=$webappAsessionId. Then, as you can see in the retrieveSessionId() method, it will check the request param if it can't find it in the cookie.
We shouldn't need to do this though, because Cookies, SHOULD work fine. I've done it before. There must be something I'm missing. Hopefully if you can send me both cookies' data, that might help.
Regards,
Les
Getting Closer ...
Les, although it still isn't working, I'm relieved by your comments (i.e., that it will work/has worked in the past). I think that I'm getting closer.
Below are the cookies that I have when I access both webapps (struts2-crud & jsf-numberguess):
Name: JSESSIONID
Content: 9ed48852-e424-4a15-b918-fecaa2a45d96
Host: localhost
Path: /jsf-numberguess
Name: rememberMe
Content: clJgEjFZVuRRN5lCpInkOsawSaKK4hLwegZK/...
Host: localhost
Path: /
Name: JSESSIONID
Content: 8c5bdb51-2475-4bc5-a279-acb1cee966ca
Host: localhost
Path: /struts2-crud
I did solve a small mystery that I was seeing yesterday. The subject for the JSF webapp always had its principals = null while the struts2 webapp (where I login and authenticate) always had it populated. While tracing through the code I discovered that the following lines in my filter did nothing:
WebRememberMeManager wrmm = new WebRememberMeManager();
wrmm.setCookiePath("/");
dwsm.setRememberMeManager(wrmm);
...
dwsm.init();
This is because the DefaultWebSecurityManager class was checking its field values
(rememberMeCipherKey, rememberMeCookiePath, rememberMeMaxAge) and then constructing an local WebRememberMeManager based on those field values and then setting the WebRememberMeManager for the DefaultWebSecurityManager. This happens in the dwsm.init() in the afterSessionManagerSet() method and subsequently "re-sets" any WebRememberMeManager that might have been previously set (i.e., in the filter). So, I replaced that code with the following and afterwards, the subject.principals was being populated in both subject objects retrieved from both webapps.
dwsm.setRememberMeCookiePath("/");
...
dwsm.init();
So it appears that the rememberme cookie is being shared, now if I can only find a way to get the subject shared somehow, then we'll be in business.
--Todd
Getting Closer ...
Sweet!
Thanks very much for pasting the cookie info - that helped me see the problem: just as the remember me cookie's path must be at the root "/", then so does the sessionId cookie as well :)
Unfortunately I don't have any passthrough methods on the DefaultWebSecurityManager for the sessionId cookie attributes like I do for the rememberMe cookie attributes. I'll add those today, to be available in the next release.
For now, the DefaultWebSessionManager used by the SecurityManager has a property: sessionIdCookieAttribute.
If you set that attribute to be an instance like this:
CookieAttribute attr = new CookieAttribute(JSecurityHttpSession.DEFAULT_SESSION_ID_NAME);
attr.setPath("/");
attr.setCheckRequestParams(false);
defaultWebSessionManager.setSessionIdAttribute(attr);
defaultWebSecurityManager.setSessionManager(defaultWebSessionManager);
Then we should be in business!
I do apologize for the confusion - this should have been easier. Thanks for being patient in walking it through with me. Hopefully it will be easier for the next person who tries this out after we summarize the process.
Thanks!
Les
Awesome!!!
I got it to work!
Many thanks for all the help, and as far as any confusion ..., that just comes with the territory. I couldn't have done it without the awesome support that was provided.
Now, I've got a small laundry list of things to do:
-Break the CacheManager back out into separate instances rather than a singleton, and make sure it still works.
-Clean up the filter code that I've been mucking with the last few days.
-Summarize this process, maybe I could post my filter code as a new topic in the jsecurity forums somewhere.
-Test clustering.
Also, thanks for that last comment about the cookie path not necessarily needing to be "/". I've run into a lot of conflicting post in various newsgroups regarding that and sharing cookies/sessions.
One last question,
I was using the latest code from the SVN repository yesterday, but switched back to the jsecurity-0.9.0-beta2 release today after I got things working. Especially after I did an SVN update of the jsecurity project, where it gave me errors on my custom realm, user, and filter classes (deprecations, un-implemented methods, etc.). Ultimately, I know it is my call, but should I be using the latest trunk code or the latest release code for jsecurity?
Thank again for all the help!
Todd Kofford
tkofford@ku.edu
Re: Awesome!!!
That's very cool! Glad to hear we were successful! :)
And as far as the SVN code vs. the latest release, we're getting very close to releasing 0.9.0 RC1 - hopefully in a week at the most. If you don't mind using SVN trunk now, I would recommend it since it would make your transition to 0.9 RC1 much easier.
We've greatly simplified the object graph dependencies as well and reduced or eliminated most if not all of the init() methods, relying on constructors to do initialization whenever possible. It feels a little more OO and not so 'Springified'. I think you'll like it :)
If you try SVN, feel free to ask any questions - you might have to change some things, but in general, configuration (both text-based and code-based) is _much_ easier than before, and we've fixed and added a few things too. Check out the Roadmap.
Regards,
Les
Re: Getting Closer ...
Just a quick clarification:
Neither cookie needs to have the path be "/" - that is just the easiest. As long as their paths match, that is what really matters.
Re: Config to ensure a single cache data source?
Note that the above solution uses JSecurity and Ehcache with no Tomcat/container specific config.
Here is an approach that don't use clustering or sockets:
Put ehcache.jar in Tomcat's root lib directory and JSecurity's failsafe ehcache.xml at the root of Tomcat's classpath, ensuring both are shared among all webapps in Tomcat. Then when creating JSecurity's EhCacheManager, you can do this:
EhCacheManager cm = new EhCacheManager();cm.setCacheManager( CacheManager.getInstance() ); //forces EhCache to statically initialize itself with an ehcache.xml file at the root of tomcat's classpath.
cm.init();
securityManager.setCacheManager( cm );
// same as above
...
When both JSecurity applications do the above code chunk, they will be using the same JVM statically-loaded ehcache CacheManager instance. That same instance is wrapped by JSecurity's EhCacheManager and everything else works the same. This means all Cache instances would be shared across the VM. No socket overhead.
This is very fast, but 1) requires tomcat-specific set up and 2) is not useful if you plan to re-locate the applications to separate machines. Also, in reality, the socket overhead for your environment on the same machine for both apps is probably negligible, such that it probably wouldn't matter much if you were using the clustered solution.
So, I guess the question to ask yourself is - would I want to spend the time to create a custom clustered ehcache config that is used by both apps? Or would I want to spend the time to configure both JSecurity apps to use the same VM memory instance via Tomcat classpath set up. The latter takes less time to set up and is faster at runtime, but not useful on anything more than the same Tomcat installation.
It might be worth just trying the latter just to get it working. Then you could fiddle with ehcache clustering or TerraCotta if you ever needed to move the apps to separate machines.
Cheers!
Les
sharing a jsecurity subject between 2 webapps
Hi Les,
Yes, sharing a jsecurity subject between 2 separate webapps is what I want to do. The topic, Session sharing, keeps coming up as a way to do this, but I just wanted to make sure that I wasn't missing a "built-in" jsecurity mechanism that would allow me to do the same thing.
I am interested in the jsecurity enterprise sessions that you mentioned above, but I'm not sure I understand the docs. Is it basically session persistence to a database or a cache on the server?
I'm also going to do some looking into TerraCotta.
We basically have 2 separate webapps that need to act as 1 integrated webapp, in as far as the security and session expiration.
As always, thanks for the quick response! I would have replied earlier, but I took off early on Friday when I posted the question for a well needed rest.
Todd Kofford
tkofford@ku.edu