Monday, July 8, 2013

JSF 2, Spring Security integration with Remember-Me (Contd.)

In last part, we said that we are going to use a database to store Remember-Me cookie's related data. This data consists of the username used to login, a series identifier and a token and a timestamp to maintain the last login to the app. For more details about the principles behind stored data, please refer to this article. Fortunately, for us, Spring Security will handle this stuff. We just need to give him the right configs.

1) Configuring database

First thing to do, is to prepare our database. In this series, I will be using MySQL as a DB.

Next, we configure a datasource to be used, not only by Spring Security, but also by future persistence frameworks (Hibernate, Spring Data etc...). In applicationContext.xml add following:

First line, is just telling Spring about a properties file contained in classpath of our application and named "application.properties". This file, will contain config params that will help system administrators to deploy our application without any need to recompile whole application when a server password is changed:

In next lines, we are just defining a Spring bean that will be our used JDBC datasource implementation. We need to pass it params like db url, username, driver class etc... Don't forget to add dependencies of your DB driver in POM file, for MySQL, just add following:

mysqlmysql-connector-java5.1.6

2) Remember-Me stuff

In previous part, we added a remember-me element to http config element. That will tell Spring Security to try to fetch user from our system whenever his browser send a valid cookie value.

Now we need to update that remember-meelement so it references a new defined Spring bean:

The remember-me services bean must define an implementation of a remember-me strategy. Here I am going to use one of the two implementations Spring Security offers. For the PersistentTokenBasedRememberMeServices to work, we must give it: a tokenRepository which is a bean we will define and that will store tokens in previously created table, a userDetailsService (and we already defined one in previous part), a key that will be used to encrypt/decrypt sent cookie to client browser. The two other properties are optional. And their names are just meaningful.
Now we define the tokenRepository bean:

We just give it the name of already configured datasource. The other property is just to prevent it to drop/create the table on each server start.

We need also to define an authentication provider related to the Remember-Me concept and register it:

And we need to register it in the authentication-manager already defined, here is the entire declaration:

And the last config we need is to define a Remember-Me filter that should intercept HTTP requests and validate cookies whenever needed:

Notice how we gave it references to our defined rememberMeServices and authenticationManager.

3) JSF 2 Remember-Me part

Now that everything is configured as expected, let's implement the JSF part of the app that should tell Spring Security when to remember a user, and when not.
First thing to do, is to inject the rememberMeServices service in the UserManagedBean managed bean:

So what does this code do ?
From the Spring Security docs, the RememberMeServices#loginSuccess method must be called "whenever an interactive authentication attempt is successful". And since we are doing login manually, then, we should invoke it manually. As for its role, this class "examines the incoming request and checks for the presence of the configured "remember me" parameter. If it's present, or if alwaysRemember is set to true, calls onLoginSucces". The onLoginSucces method by itself is responsible for creating a new persistent login token with a new series number. Then it stores the data in the persistent token repository and adds the corresponding cookie to the response.
The loginSuccess method must be given a HttpServletRequest to check if a remember-me param is present and if it's equal to "true", if not it will just return. Second param is a HttpServletResponse, in which generated cookie is written, and finally a org.springframework.security.core.Authentication that will contain username of loggedin user.
Now it should be clear why I overrided the getParameter method in the HttpServletRequestWrapper passed to loginSuccess.
The added code by now, will activate the Remember-Me whenver called. This should not be the case, since not all users will wish their connections be maintained (they may connect from a public PC for example). This been said, we need to test if connecting user wants to be remembered:

Now we just test on the rememberMe value to decide whether to call RememberMeServices#loginSuccess or not.
Et voilà!! We just finished with the Remember-Me feature in JSF 2 with Spring Security.
You can test your code by setting a session timeout value of 5 minutes for example, login to the application and wait for session to timeout and then re-call home page, you should access it without being obliged to re-login.

4) Instantiate JSF Managed beans

Now suppose, that with normal login, if user is successfully authenticated, you have a session managed bean that will handle some informations to be used by other managed beans in the application. You may say that we already have the logged in user's username in Spring Security' SecurityContextHolder. That's true, but also, the stored object has a few data, and you will soon be obliged to fetch more data for the User object from DB. That will result in extra DB access.

We inject this bean in the UserManagedBean (view managed bean responsible for the login/logout process), and whenever a successful login takes place, we set user in LoggedInUser with the returned object from DB. And in all managed beans that need informations about user logged to the app, we just inject this managed bean.

Now if you try to access the application after session timeout, Spring Security will create a new session for you and authenticate you automatically. But in this newly created session, you won't have JSF session managed bean LoggedInUser. And if you try to access its User variable in a homeManagedBean, you will have a NullPointerException.
To initialize this bean, in early defined CustomUserDetailsService add the following code after setting authentication in SecurityContextHolder:

Thanks to Arjin comment below, this code will only work if the LoggedInUser objects are simple POJOs with no injected beans. But when we want to use other services inside it (as for example: injecting the RememberMeServices bean to centralize the updating process of SecurityContext, which I did indeed) then, things will go bad and complicated. And all of that if because of LoggedInUser object being manually instantiated.
The solution I opted for in this situation is to inject a scoped proxy of LoggedInUser inside CustomUserDetailsService. You would say, "but CustomUserDetailsService is a singleton service that will be created only once a time, however LoggedInUser is a session scoped bean, that will be created on every new session". And that's why I am using a scoped proxy of LoggedInUser. By that we will be "injecting a proxy object that exposes the same public interface as the scoped object but that can also retrieve the real, target object from the relevant scope" (more details here). So how to do it ?
First let's create an interface that LoggedInUser will implement:

Two things are to be noticed here:
a) we are using "proxyMode = ScopedProxyMode.INTERFACES" to tell Spring that we are exposing this bean as a proxy
b) we moved the logic to set authentication in Spring Security context from view managed beans (ex: in UserManagedBean) into the LoggedInUser.
This been done, you just update managed beans containing references to LoggedInUser to reference the new defined ILoggedInUser instead, example in UserManagedBean:

13 comments:

Nice article, but I would think twice about manually instantiating LoggedInUser and putting the result of that into the session directly. This will work with the given example, but will break as soon as LoggedInUser starts to use other (CDI) services, e.g. is injected itself with some beans and/or uses interceptors.

With the code as given, sometimes LoggedInUser will be a proxy and sometimes it will not be one.

I think you can better try to resolve the EL expression "loggedInUser" or inject the bean. It will always be non-null then. After that you can set the User if it's null.

Hi Arjan,I really appreciate your comment. I totally agree with you about this point. And I think using a scoped proxy of the LoggedInUser object is a best solution for it. Isn't it ?I will update the article with modifications right now.Again, thank you for your remarkhttp://static.springsource.org/spring/docs/3.0.x/reference/beans.html#beans-factory-scopes-other-injection

Hi Narendar and welcome to my blog,In the class BaseManagedBean (from which almost all my managed beans inherit) you can find a method named updateSecurityContext that calls a method from the injected ILoggedInUser bean, updateSecurityContext.Now, in LoggedInUser managed bean (implementation for the ILoggedInUser interface), you can find the remember-me logic. As for the check box, it's very easy to add a checkbox to your jsf login view and then test its value in the managed bean to decide whether or not to remember the user

Hi,Thanks for the information.But when I try to login its giving me below exception:/pages/user-profile.xhtml @22,75 rendered="#{loggedInUser.user.hasRole('ROLE_USER')}" Failed to parse the expression [#{loggedInUser.user.hasRole('ROLE_USER')}]

Ok, sorry for being this late. I have been very busy at work. Anyway, I really can't tell about the real cause of this exception unless you give full stack.However, my first guess is about your EL version (may be you are using Tomcat6 ?)