Using JNDI to share servlet session objects and data in Tomcat - java

Over the last few weeks i have been looking at solutions to share an object between two contexts/war files. There are a number of ways this can be done and one of them is JNDI.
I am not very familiar with JNDI used in Tomcat so would like a few questions clarified:
Basically i have an instance of an object that would provide the following services to more than one context/application
Check that the user is logged on
Check that the user's session is valid
Logon user - Includes logging the logon details
Logout user - Remove session
Each application will call this object to validate the user before it processes any requests. What i dont understand is how the object would work if it is stored on JNDI. I have seen a few examples of how JNDI is used in Tomcat but 99% of the examples show how to configure a JDBC datasource.
How exactly is the object initialised in JNDI. For example, the following configuration from the Tomcat docs shown the configuration for JDBC
<Resource name="jdbc/db1"
auth="Container"
type="oracle.jdbc.pool.OracleDataSource"
driverClassName="oracle.jdbc.driver.OracleDriver"
factory="oracle.jdbc.pool.OracleDataSourceFactory"
url="jdbc:oracle:thin:#oracle.microdeveloper.com:1521:db1"
user="scott"
password="tiger"
maxActive="20"
maxIdle="10"
maxWait="-1">
How would i do a similar thing in my case and most importantly, how would i initialise the object before it is put on the JNDI tree. Once it is on the JNDI tree, how is it updated?
I guess what i am looking for is a simple example of the use of JNDI in Tomcat but not for database connections but for service provider type objects.
I have been reading this tutorial http://docs.oracle.com/javase/tutorial/jndi/ops/bind.html but it is focused more on LDAP naming directories which doesnt really help.
Edit
Ok i did find an example in the tutorial i listed above that shows how to "bind" an object the the JNDI tree.
class SerObj {
public static void main(String[] args) {
// Set up environment for creating initial context
Hashtable<String, Object> env = new Hashtable<String, Object>(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");
try {
// Create the initial context
Context ctx = new InitialContext(env);
// Create object to be bound
Button b = new Button("Push me");
// Perform bind
ctx.bind("cn=Button", b);
// Check that it is bound
Button b2 = (Button)ctx.lookup("cn=Button");
System.out.println(b2);
// Close the context when we're done
ctx.close();
} catch (NamingException e) {
System.out.println("Operation failed: " + e);
}
}
}
How would i modify it to use the Tomcat environment instead of the LDAP environment as shown above?
Does the bind store a copy of the object or a reference to the object? What i am not sure of is for example, if 3 webapps access the object, will they all be accesssing the same instance of the object or different instances? I would like there to be only one instance of the object i put in the JNDI tree.
Edit
I must be missing something very simple. Here is another example i found which shows how to configure a JNDI object in Tomcat - http://flylib.com/books/en/4.255.1.275/1/
How is the com.jspservletcookbook.StockPriceBean initialised?
If one webapp uses the com.jspservletcookbook.StockPriceBean object and changes its state, is this change reflected across all other webapps that access this object?
And most importantly, how many instances of the com.jspservletcookbook.StockPriceBean will be created - I need it to be the same instance for all webapps that would access the object.
Edit
#Ben Brunk - At the moment the session data is stored in a Map which is managed by one of the applications(context). All i need is for the other applications to call this application to check if the session is valid. My problem is that for these applications to get the services of the application that manages the session data, i need to use JNDI for them to get a reference to the relevant object.
The data will not be stored in JNDI. I just want to use JNDI as a means for the applications to find and use the Session Manager object. The problem though is that everything i read about JNDI leads me to believe that it is used to create new objects every time an object is searched or 'looked up' in the directory.
Here is diagram i think shows exactly what i want to achieve:
Edit
#EJP - Just to clarify, the functions i listed above are just examples of what the shared object might do. These were provided just as an example. All the functions it would do are related to the user and the logged on session. For example things like account information(username, last login etc), password information (expiry date etc), user privileges (i.e. which areas of the application the user is allowed access to).
Given that the question is really related to multi-context communication via JNDI (Or any other means) and not about the requirements of the object it doesn't really make sense to list everything that the object is supposed to do so i just listed examples of what it could do.
Apologies if the way i put the question confused anyone.

If you don't care to use LDAP, just use the database itself by creating tables in there to manage session information. You should be able to find plenty of examples of how to do that because it is a common need in enterprise applications. In fact, I had not seen an example of doing it using the LDAP directory before. It seems like LDAP would be a bad fit for managing sessions across domains because it really isn't designed to be updated a lot.
Just to be clear: JNDI is not a storage mechanism. It is a mechanism for naming resources in an enterprise environment and then having the container manage connections to those resources. You need to store your session data in a database or you could use the filesystem or a NoSQL map type solution.

There are several other ways of accomplishing this.
Use Tomcat Container Managed Authentication in association with JAAS. Then your module becomes a JAAS LoginModule.
Use Tomcat Container Managed Authentication with the Tomcat Single Sign-on module.

Related

Keycloak - how to handle multiple work contexts

I have an application where single user can work in contexts of multiple companies. We call such a connection (user<->company) a permit. Every one of this permits can have different sets of permissions/roles. We want user to login just once and then he can simply change permits within application without need to enter password again.
Till now we had only one application and kept this whole permission model in our own DB. Unfortunately now we have to support second application which should inherit those permits. I was wondering wether is possible to move that model to keycloak so we don't have to replicate it to every single db and keep it in sync manually.
I have searched keycloak documentation regarding this topic but have found no information att all, which seems quite odd, because I don't think we are the first one working with multiple context application.
So now I'm asking is it possible to configure our model in keycloak and if so, how to do it? Eventually are there different options? I guess that I can provided that model as a claim with json structure but that doesn't feel right to me. I was thinking about custom IDP which could provide such claims based on DB so there no spelling errors and less repetition but I feel there should be a better way.
You could try to write your own Keycloak provider (SPI). There is a built in mechanism that allows you to expose REST endpoint on the Keycloak: https://github.com/keycloak/keycloak/tree/master/examples/providers/domain-extension
That REST could be called with authorized context only for example by passing Access-Token (Authorization header with Bearer value). On the provider level (through implementation of: org.keycloak.services.resource.RealmResourceProviderFactory and org.keycloak.services.resource.RealmResourceProvider) you have access to user's Keycloak session and object UserModel like in the following code:
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(keycloakSession, keycloakSession.getContext().getRealm());
UserModel userModel = authResult.getUser();
UserModel class has methods for getting and setting attributes, so some information that indicates the current permit/company ID can be stored there. You can use REST methods exposed on the Keycloak to modify the model within the 'session' (represented by Access-Token).
The Github example shows also how to use another Keycloak provider (ex. built-in JPA provider) from you custom provider's level, so using that approach you could try to connect to the database with your permits/company informations. Of course the datasource representing you database should also be registered as Keycloak datasource.

How to add string parameters in lookup method in jdbc connection?

In my web application I'm using JDBC connectivity basically its working fine with below code
connObj = DriverManager.getConnection(dbaseUrl, usrName, Paswrd);
But when I'm using veracode tool its showing flaw as J2EE Bad Practice:getConnection so that i need to implement.
InitialContext ctx= new InitialContext();
DataSource dsrc=(DataSource)ctx.lookup(dbaseUrl, usrName, Paswrd);
dsrc.getConnection();
How can I pass 3 parameters in lookup so that it should not disturb my previous flow of code. Can anybody guide me please?
You cannot change the lookup method parameter. But you can use bind or rebind methods of InitialContext to retrieve your datasource.See sample
ctx.bind("java:/comp/env/jdbc/nameofmyjdbcresource", dsrc);
For details example check here.
A DataSource is a connection to one database and you configure a DataSource with a single username and password. If you really need specific (and changing) usernames/passwords depending on application logic, then you can use DataSource.getConnection(String username, String password). However keep in mind with some (most?) connection pools this will give you a non-pooled connection.
If you want to access a different database (or a different configuration), then you need to specify a data source for each database you want to access and ask for that specific data source.
If that is not possible for your situation, then you should just ignore/suppress the veracode warning and continue using DriverManager.

Testing the database connection with spring and hibernate

I'm currently working on a java application. It's a standalone client with Spring and Hibernate. Also C3P0.
In the previous version we used a standard user(hardcoded in the configuration file) for the database connection but now we changed it so that every user has to provide his own credentials.
The beans with the code for the database are basically created on-demand.
I changed the XML-files and added a postprocessor which sets the credentials as well as some connection settings. It looks similar to this now:
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext();
configurer = new PropertyPlaceholderConfigurer();
// properties are retrieved from a private method
configurer.setProperties(getProperties());
context.addBeanFactoryPostProcessor(configurer);
context.setConfigLocations(new String[] { "first.xml","second.xml" });
context.refresh();
return context.getBean("myClass", MyClass.class);
This all works as expected but now we reach the part where I'm currently stuck.
I want to provide a test functionality for the connection, so that the user can click a button and then is told if his credentials were accepted or not.
My first idea was to do a simple select on the database. Sifting through the logs however, I noticed that Spring tries to connect to the database during the refresh() (or rather the instantiation of the bean) anyway. I can see exceptions in the logs, for example: java.sql.SQLException: Connections could not be acquired from the underlying database!
Unfortunately, Spring doesn't seem to actually care. The exceptions are logged away but refresh() finishes and is not throwing any exceptions because of this. I had hoped that I could simply catch the exception and then I would know that the connection failed.
I could just do the select as planned, but I want to limit the connection attempts as much as possible, because the database server will block the user after several attempts. Even permanently if there are to many attempts(already had some fun with that, before I changed the settings for the connection pool).
My searches regarding this problem came up with practically nothing. Is there a way to get the exception somehow? Or does Spring provide an API of sorts that would tell me about the connection error during the instantiation/refresh?
Failing that, any ideas for an alternative approach? Preferably one that needs only a single attempt to determine if a connection is possible.
Edit: For anyone interested: I went with the suggestion from Santosh and implemented a connection test in JDBC.
Unfortunately there seems to be no easy way to make use of the database errors/exceptions encountered during the bean instantiation.
The kind of functionality you are looking for would be very tricky to accomplish using spring+hibernate.
The connection properties are set at the session-factory level and if credentials are incorrect, the session-factory is not instantiated.
Quoting #Bozo from his answer here.
What you can do is extend LocalSessionFactoryBean and override the
getObject() method, and make it return a proxy (via
java.lang.reflect.Proxy or CGLIB / javassist), in case the
sessionFactory is null. That way a SessionFactory will be injected.
The proxy should hold a reference to a bare SessionFactory, which
would initially be null. Whenever the proxy is asked to connect, if
the sessionFacotry is still null, you call the buildSessionFactory()
(of the LocalSessionFactoryBean) and delegate to it. Otherwise throw
an exception. (Then of course map your new factory bean instead of the
current)
There is also a simple and rudimentary approach wherein before creating ClassPathXmlApplicationContext, simply try to obtain a connection using raw JDBC calls. If that succeed then proceed or else give use appropriate message.
You can limit the connection attempts here as you are in full control.

Concept for reusable login session in rmi ejb calls

This is not a simple question its just because i'm rethinking our architecture for securing our EJB 3.0 service by a login and security.
We have a EJB3.0 application on JBoss 5.1 that offers various services to a SWT client to read and write data. To use a service, the client must login with a valid user and password which is looked up by SpringSecurity in a LDAP server. SpringSecurity generates a session id which is passed back to the client to be resused in any further service call.
client server
| |
|-> login(user/password)-------->|
| |
| <------- sessionId ------------|
| |
|-->serviceXy(sessionId,param1)->|
The situation seems clear. We store the sessionId in our own context object which is the first parameter of each service method. There is an interceptor on each service method which reads the sessionId from the given context object and checks if the session is still valid. The client needs to call the login service first to get a context object filled with the sessionId and reusue this context object in further service calls.
public class OurContext {
private String sessionId;
}
#Stateless
#Interceptors(SecurityInterceptor.class)
public OurServiceImpl implements OurService {
public void doSomething(OurContext context, String param1) {
[...]
}
}
The thing i don't like at this solution is the polution of each service method with the context parameter.
Isn't there a similar mechanism like a http session in rmi calls? I'm thinking of putting our context object in some kind of session that is created in the client(?) right after the login and is passed to the server on each service call so that the SecurityInterceptor can read the sessionId from this "magic context".
Something like this:
OurContext ctx = service.login("user","password");
Magical(Jboss)Session.put("securContext", ctx);
service.doSomething("just the string param");
Since you are already using an app server, it seems that you should be using the built-in EJB security mechanisms, generally provided through JAAS. On the 4.x jboss line, if you implemented your own JAAS plugin for jboss, you could get access to a "special" context map (similar to what you describe) which is passed along on remote requests (by the jboss remote invocation framework). I haven't used jboss in a while, so not sure how this maps to the 5.1 product, but i have to imagine it has similar facilities. This assumes, of course, that you are willing to implement something jboss specific.
There are some kinds of session mechanisms in EJB, but they all start when the remote call starts, and ends when that ends. On old one is the transaction context ( Adam Bien wrote about this some time ago), and a newer one the CDI Session Scope.
Contrary to popular belief, this scope doesn't just mirror the http session scope, but in absence of an http session (like for remote calls), it represents a single call chain or message delivery (for mdbs).
With such a session, your remote SWT client still has to pass the sessionId to the remote service, but any local beans called from there can pick it up from this "cdi" session.
The other option is kinda like what jtahlborn says: with your own login module you can return a custom principal, instead of the default one. Your code can first request the normal principal and then try to cast it.
The problem is that this stuff is container specific and JBoss always forgets about it. It pretty much breaks after every update, and users have to kick and scream to get it fixed in some next version (only to see it break again in the version after that). Without JBoss really supporting this it's an endless battle.
Yet another option is to let the user login with the sessionId as name. The login module behind that could be a simple module that accepts everything and just puts a principal in the security context with the sessionId as 'name'. It's a little weird, but we've used this succesfully to get any data that can be expressed by a string into the security context. Of course, you would need to let your client do a regular container authentication here, which kinda defeats using Spring security in the first place.
We went for another approach which is portable and does not rely on a specific app server. In addition our security implementation frees us from the restrictions of the EJB approach (which by the way I thought were closed 2 decades ago ... but came up again).
Looking top down:
There is a server providing classes with methods to work on same data.
The client(s) provide the data and invoke specific methods.
Our approach is to put all data (and therefore communication between client and server) into a "Business Object". Every BO extends a superclass. This superclass contains a session id. The login method provides and returns that id. Every client only has to ensure to copy the id received in one BO into the next one it sends to the server. Every method which can be remotely (or locally) invoked, first obtains the session object with the received id. The method to return the session object also checks security constraints (which are based on permissions and not on roles like in the EKB approach).

How do I get a list of all HttpSession objects in a web application?

Let's say I have a running Java-based web application with 0 or more valid HttpSession objects associated with it. I want a way to access the current list of valid HttpSession objects. I was thinking that I could implement an HttpSessionListener and use it to append to a list of session id values that are stored in an application-scoped attribute, but then I'm on the hook to update the list as sessions are invalidated and who knows what else.
Before I start baking my own solution I thought I should ask the question:
Does the servlet API provide some means of getting access to the complete list of non-invalidated session objects?
I am using Tomcat 6.x as my web application container, and the MyFaces 1.2.x (JSF) library.
SOLUTION
I followed an approach similar to what BalusC discussed in these existing questions:
How to easily implement "who is
online" in Grails or Java Application
?
JSF: How to invalidate an
user session when he logs twice with
the same credentials
I modified by SessionData class to implement HttpSessionBindingListener. When a binding event happens, the object will either add or remove itself from the set of all the SessionData objects.
#Override
public void valueBound(HttpSessionBindingEvent event) {
// Get my custom application-scoped attribute
ApplicationData applicationData = getApplicationData();
// Get the set of all SessionData objects and add myself to it
Set<SessionData> activeSessions = applicationData.getActiveSessions();
if (!activeSessions.contains(this)) {
activeSessions.add(this);
}
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
HttpSession session = event.getSession();
ApplicationData applicationData = getApplicationData();
Set<SessionData> activeSessions = applicationData.getActiveSessions();
if (activeSessions.contains(this)) {
activeSessions.remove(this);
}
}
The one thing that continues to irritate me is what happens when Tomcat is restarted. Unless Tomcat has been properly configured to NOT serialize sessions to disk, it will do so. When Tomcat starts up again, the HttpSession objects (and the SessionData objects along with them) are deserialized and the sessions are made valid again. However, the serialization/deserialization totally sidesteps the HttpSession listener events, so I do not have the opportunity to gracefully put the deserialized reference to the SessionData back in my managed Set of objects after the restart.
I don't have any control over the production configuration of Tomcat in my customer's organization, so I cannot assume that it will be done the way I expect it.
My workaround is to compare the HttpSession creation time with the application startup time when a request is received. If the session was created before the application startup time, then I call invalidate() and the user is sent to an error/warning page with an explanation of what happened.
I get the application startup time by implementing a ServletContextListener and storing the current time inside an application-scoped object from within the contextInitialized() method of my listener.
No, the Servlet API doesn't provide a way. You really have to get hold of them all with help of a HttpSessionListener. You can find several examples in the following answers:
How to find HttpSession by jsessionid?
How to find number of active sessions per IP?
How to check Who's Online?
How to invalidate another session when a user is logged in twice?
There is no straight forward way. It depends on deployment. Above will fail once you decide to introduce distributed deployment and load balancing.
Not really an answer, but in the good ol' days there was "javax.servlet.http.HttpSessionContext", but it was dropped as of version 2.1, explicitly with no replacement: https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/http/HttpSessionContext.html

Categories