Monday, July 15, 2013

@TransactionManagement doesn't work in JBoss 6 Final

Only for the record. Here it is a link of a post in the Jboss community about a bug I've found. This cause me some headache. ;)

https://community.jboss.org/message/588983

Here it is the bug filled: https://issues.jboss.org/browse/EJBTHREE-2238

Friday, March 8, 2013

JAAS Persistent sessions with JBoss 6

A common issue you can found when your web application is becoming more popular is how to update it without disturbing your users.
Redeploying a standard application could take arround 5-10 secs in a JBoss6, so we could assume that down-time for a monthly basis update.

But, what happens with logged-in users when you update your application? Well, they will lost their session and will need to login it again. We could assume that for a couple of redeploys, but at the end you will notice that you are so reticent to update your application because your users will suffer it.

If you look at google for jboss persistent sessions, you will found that it is so easy to solve this problem. In the most tutorials you will be encouraged to save the session data to disk, so when you redeploy your application, the session will not be lost. Configure jboss to that is so simple, in server/default/deploy/jbossweb.sar/context.xml you will see a comment saying that session persistence is disabled and what only you need to enable it is uncomment the following line:

  <Manager pathname="SESSIONS.ser" />

Now, the session will be serialized to that file, you should take in account that your session data must be serializable, but in most cases it is. You could even change the path of the SESSIONS.ser since it is saved on a temporary directory and it will be deleted on jboss restarts. To avoid this, simply set an absolute path:

  <Manager pathname="/opt/app/data/SESSIONS.ser" />

That is all, so easy doesn't it? Well, but... This doesn't work, at least if you are using JAAS to authenticate your users. The reason because this will not work if you are using JAAS is because the session is stored correctly but the JAAS principal is not, and JAAS will discard the session for users without a principal. So at this point, you have two options:
  • Not use JAAS as authentication framework.
  • Assume that your users will lost their session when you redeploy your application.
  • Or...
Create a jboss cluster. I haven't evaluate this option before because seemed I was complicating a lot the infrastructure for a simple web app. But I was really frustrated by the fact I was unable to update the application without disturbing my users. So I was decided to give it a chance.

I was starting reading a couple of tutorial on how to setup a cluster and seemed to be very easy.
Since my intention with the cluster was only maintain the users logged in when redeploy my application. I was only require two nodes on the cluster. If I use two ethernet interfaces I could maintain the two nodes in the same server, simplifying the overhead of this configuration.

This is the recipe I've used to configure two nodes of a JBoss Cluster to avoid JAAS principal lost:

Changes in the application

We don't need to change any code of our application, simple add this line to the web.xml to inform to jboss that the application is clusterizable:
        <distributable/>
In most cases you need to do nothing else in your application.

Changes in the server

Simply configure a virtual ethernet interface assigning an additional IP:
For ubuntu/debian, in /etc/network/interfaces

auto eth0
iface eth0 inet static
        address 172.26.1.1
        netmask 255.255.255.0
auto eth0:2
iface eth0:2 inet static
        address 172.26.1.2
        netmask 255.255.255.0

Configure the JBoss

There is not so much to configure, create two separate installations of jboss, for example:

/opt/jboss-node1
/opt/jboss-node2

And use the following run scripts for each one:

/opt/jboss-node1/bin/run.sh -c all -b eth0 -g <cluster_name> -Djboss.messaging.ServerPeerID=1 -Djboss.jvmRoute=<node1_name>
/opt/jboss-node2/bin/run.sh -c all -b eth0:2 -g <cluster_name> -Djboss.messaging.ServerPeerID=2 -Djboss.jvmRoute=<node2_name>

You must replace the values between <> with your specific values.
Cluster_name Is a name you want to give to your cluster. Since the cluster will autodiscover all nodes in the network with the same cluster name, you will want to have a special name to separate dev and production clusters in the same network.
The ServerPeerID and node_name are unique for each node, you can select whatever you want.
The jvmRoute name allows us to associate all request from a specific user to the same server. In server/all/deploy/jbossweb.sar/server.xml locate the line:

 <Engine name="jboss.web" defaultHost="localhost">

and change it as:

 <Engine name="jboss.web" defaultHost="localhost" jvmRoute="${jboss.jvmRoute}">

Finally, you need to enable the ClusteredSingleSignOn valve in jboss.web to activate the JAAS principal propagation. Go to server/all/deploy/jbossweb.sar/server.xml and uncomment the line:

<Valve className="org.jboss.web.tomcat.service.sso.ClusteredSingleSignOn" />

Now, you can deploy your application on server/all/deploy, this should work as always.

Configure the balancer

If you have deployed your application on both servers  you will be able to reach your application by pointing your browser to 172.26.1.1 or 172.26.1.2 On that point, you only need to balance the incoming users to each node, and of course if one of the nodes goes down the users on that node will be moved to the other node. And the most important: The users will not notice the change.

The balancer I'm using is mod_jkyou can install it on debian/ubuntu with:

apt-get install libapache2-mod-jk

Then, proceed as always to enable the module on your apache-httpd.
Configure the mod_jk to register the nodes on the cluster and how will be reached:

workers.properties:
worker.<node1_name>.type=ajp13
worker.<node1_name>.host=172.26.1.1
worker.<node1_name>.port=8009
# worker.<node1_name>.fail_on_status=404

worker.<node2_name>.type=ajp13
worker.<node2_name>.host=172.26.1.2
worker.<node2_name>.port=8009
# worker.<node2_name>.fail_on_status=404

worker.mycluster.type=lb
worker.mycluster.balance_workers=<node1_name>,<node2_name>
worker.mycluster.sticky_session=1

As you can see, we have defined our two jboss nodes, and a cluster called mycluster that will balance the incoming requests alternatively to both nodes node1 and node2. If a node is unreachable it will be marked as down and will no receive requests anymore until becomes active again. We can also use the fail_on_status to force a node status down when the node returns the specified error codes. (can be a comma separated list).
The sticky_session will maintain the users in the same server.

Now, we can mount mycluster to a specific url on our apache-httpd with the following configuration. You can put in your virtual server configuration:

    JkMount       /yourapp   mycluster
    JkMount       /yourapp/* mycluster

As you can see, we have made available our cluster in the http://apache_url/yourapp url. If you access to that url, mod_jk will redirects you to any of the two server nodes. If that node fails it will redirect to the other node.

The advantage here is the jboss cluster will maintain http session and JAAS principals in all cluster nodes, so if one node goes down, the other node can maintain the users authenticated.

I have using this configuration for a while and works really well, and doesn't requires more maintenance or attention that a simple jboss. 

Thursday, November 8, 2012

Jasig CAS

In most cases the login code of your application does not require thinking a lot. Simply use the standard module available on your framework, that is all you need.

But sometimes, you need to integrate third-party applications with your own. For example a cms, forums, ticketing systems, etc. Your users can stand a couple of months (in the best case) entering two times the user/password. But you know it is something you need to change.

At the beginning someone could be tempted to implement your own solution because seems it is very simple. Simply read one cookie from here and put there, and then redirect to there and back to... Well, at the end you will see yourself hacking every third-party application, and learning new concepts as man-in-the-middle, spoofing, etc. That is good, of course. But could be even more good if you can learn it with a bit less pressure because you have been so optimistic when you schedule this feature.

What I use in that cases is a SSO server. This allows me to leverage of SSO functionality on my application in a fraction of time if I'd try to implement by myself.
Jasig CAS (Central Authentication System) lets you to integrate it easily on your java web applications, but also in other platforms as PHP, .NET an others. The protocol is well documented and can be implemented in any platform if it is not available. On the other side, you have a lots of CASified applications which is how they name the clients that are been integrated into CAS.

Here we will see how can we use CAS on our JBoss6 applications. But, first of all you should start reading https://wiki.jasig.org/display/CASUM/Demo and try to become familiar with the CAS use.

Then, when you feel prepared to try it out with your JBoss/JAAS application follow the next steps. Remember you need to have a CAS server already up and running and reachable from http://yourcasserver/cas so, lets go:
  • Copy the cas-client-core-<version>.jar and cas-client-integration-jboss-<version>.jar to the server/default/lib dir.
  • Configure de JAAS login-config module as follows:
    <application-policy name="cas">
       <authentication>
          <login-module code="org.jasig.cas.client.jaas.CasLoginModule" flag="required">
             <module-option name="ticketValidatorClass">
               org.jasig.cas.client.validation.Cas20ServiceTicketValidator
             </module-option>
             <module-option name="casServerUrlPrefix">
                http://yourcasserver/cas
             </module-option>
             <module-option name="tolerance">20000</module-option>
             <module-option name="defaultRoles">admin,user</module-option>
             <module-option name="roleAttributeNames">role,list</module-option>
             <module-option name="principalGroupName">CallerPrincipal</module-option>
             <module-option name="roleGroupName">Roles</module-option>
             <module-option name="cacheAssertions">true</module-option>
             <module-option name="cacheTimeout">480</module-option>
          </login-module>
       </authentication>
    </application-policy>
  • Modify the deploy/jbossweb.sar/server.xml and uncomment:
    <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
  • Finally in the web.xml of your xml you need to configure the servlet filters:
    <!-- Facilitates CAS single sign-out --> <listener> <listener-class> org.jasig.cas.client.session.SingleSignOutHttpSessionListener </listener-class> </listener> <!-- Following is needed only if CAS single-sign out is desired --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <!-- Only 2 CAS filters are required for JAAS support --> <filter> <filter-name>CASWebAuthenticationFilter</filter-name> <filter-class>org.jasig.cas.client.jboss.authentication.WebAuthenticationFilter</filter-class> </filter> <filter> <filter-name>CASAuthenticationFilter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://cas.example.com/cas/login</param-value> </init-param> </filter> <!-- Other filters as needed --> <!-- CAS client filter mappings --> <!-- The order of the following filters is vitally important --> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CASWebAuthenticationFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping> <filter-mapping> <filter-mapping> <filter-name>CASAuthenticationFilter</filter-name> <url-pattern>*.do</url-pattern> </filter-mapping>

    In your WEB-INF/jboss-web.xml select the appropriate security domain:
    <jboss-web>
    <security-domain>java:/jaas/cas</security-domain> </jboss-web>
  • The first time, may be you could want to activate the trace logging to inspect any error you could get in your JBoss app. In server/default/deploy/jboss-logging.xml do it by adding:
    <logger category="org.jasig">
       <level name="TRACE" />
     </logger>
That is all. Start the server and make some tests. You have all of this more detailed in:

Friday, October 12, 2012

How not to design a IndexedDB HTML5 API

Today I'm playing a little with the IndexedDB API and I was a bit confused about how it has been designed.
The antecedents: Since the IndexedDB API is asyncronous, when you call to a method, for example open(), it won't be blocked waiting for the results of the function. Instead, you will get a request object, where you can associate callbacks to receive errors, results, or other kind of events. You can see here how it is done:
  1. var request = indexedDB.open("TestIndexed", 1);  
  2. request.onsuccess = function(evt) {
  3.     console.log("db sucessfully opened");
  4.     db = request.result;                                                            
  5. };
As you can see, we call to the open() method, immediately it returns a request object where we can install an onsuccess callback that will be called if the open operation was successfully completed. What confused me was that if the open() method send an event before you has been installed the callback you will lose the event. We can argue that this shouldn't ocurr because if you install the event just after the method call, you will stay almost sure that the database operation will spend more time that assign the callbacks.
I agree with that, but anyway I don't like that my API interface works only because I suppose some methods will take more time than others.

The problem is that if you put some code between the database open() and the callbacks, you will get in trouble. Every browser will take more or less time on the open() operation. For example, on my firefox browser took arround 70 miliseconds and in chrome took arround 120 milisecs. So, if you have something like this:
  1. var request = indexedDB.open("TestIndexed", 1);  
  2. foo();
  3. request.onsuccess = function (evt) {
  4.     db = request.result;                                                            
  5. };
Depending the time take the foo() method, this code might or might not work on a specific browser.
I know it hasn't sense do something like that. But for that reason, the design of the API should reinforce you to prepare all your data and events before the method call. It is really ugly prepare your events after calling the method. A better approach to this scenario could be:

  1. var request = indexedDB.open("TestIndexed", 1)
  2. request.onsuccess = function(evt) {
  3.     console.log("db sucessfully opened");
  4.     db = request.result;                                                            
  5. };
  6. request.onerror = function(evt) {
  7.     console.log("error");                                                      
  8. };
  9. request.perform() // This invoke the operation
Or even better:

  1. var callbacks = {};
  2. callbacks.onsuccess = function (evt) {
  3.     db = request.result;                                                            
  4. };
  5. indexedDB.open("TestIndexed", 1, callbacks);  
In this case you are being forced to define your callbacks before calling the method, so you will never lose any event, and it is more clear, elegant and follow the same convention that others functions in  Javascript.

In order to solve that issue, I'm using this class to call to IndexedDB API methods:
  1. var AsyncCall = {
  2.     call: function(request, callbacks) {
  3.         for(var c in callbacks) {
  4.             request[c] = callbacks[c];
  5.         }
  6.     }
  7. };
Now, we can use the IndexedDB API as follow:
  1. AsyncCall.call(indexedDB.open("TestIndexed", 1), {
  2.     onsuccess: function(evt) {
  3.         console.log("ok");
  4.     },
  5.     onerror: function(evt) {
  6.         console.log("error");
  7.     }
  8. });