Implementing Spring Security Remember Me with JSF 2.0

Today we will add remember-me functionality to our project.

We will enhance our code from earlier revision, namely:

  1. Integrating Spring Security 3.1 and JSF 2.0
  2. Spring Security 3.1 and JSF 2.0 Custom Form
  3. Adding Spring Security Logout Functionality to JSF 2.0

Remember me functionality adds a expriry timeout to a cookie which is set on the client machine, when client tries to login, cookie is checked, if it has expired client is requested to login or else client is automatically logged in.

Let’s start adding this functionality.

First update our jsfspring-sec-bean-config.xml and add rememberMe services:

jsfspring-sec-bean-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:sec="http://www.springframework.org/schema/security"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd"
	   >

	<beans:bean id="navigator" name="navigator" class="com.mumz.jsfspringsec.beans.Navigator" scope="session">
	</beans:bean>

	<beans:bean id="loginBean" name="loginBean" class="com.mumz.jsfspringsec.beans.LoginBean" scope="prototype">
		<beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
		<beans:property name="rememberMeServices" ref="rememberMeServices"></beans:property>
	</beans:bean>

	<beans:bean id="rememberMeServices"
		class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
		<beans:property name="key" value="jsfspring-sec" />
		<beans:property	name="userDetailsService" ref="userDetailsService" />
		<beans:property	name="alwaysRemember" value="true" />
		<beans:property	name="tokenValiditySeconds" value="60" />
	</beans:bean>

	<beans:bean id="rememberMeAuthenticationProvider"
		class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
  		<beans:property name="key" value="jsfspring-sec"/>
	</beans:bean>

	<beans:bean id="rememberMeFilter"
		class="org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter">
  		<beans:property name="rememberMeServices" ref="rememberMeServices"/>
  		<beans:property name="authenticationManager" ref="authenticationManager" />
	</beans:bean>
</beans:beans>

Second we will update our jsfspring-sec-security-config.xml and add authentication-provider.

jsfspring-sec-security-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
	xmlns:sec="http://www.springframework.org/schema/security"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xsi:schemaLocation="
	   http://www.springframework.org/schema/beans
	   http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
	   http://www.springframework.org/schema/security
	   http://www.springframework.org/schema/security/spring-security-3.1.xsd">

	 <sec:http auto-config="true" use-expressions="true">
		<sec:intercept-url pattern="/pages/secure/**" access="hasRole('ROLE_USER')" />
		<sec:intercept-url pattern="/pages/unsecure/**" access="permitAll"/>
		<sec:intercept-url pattern="/pages/common/**" access="permitAll"/>
		<sec:intercept-url pattern="/**" access="permitAll"/>
		<sec:form-login login-page="/pages/common/login.jsf"/>
		<sec:remember-me key="jsfspring-sec" services-ref="rememberMeServices"/>
		<sec:logout
			invalidate-session="true"
			delete-cookies="JSESSIONID,SPRING_SECURITY_REMEMBER_ME_COOKIE"
			logout-success-url="/pages/common/login.jsf"></sec:logout>
	</sec:http>

	<sec:authentication-manager alias="authenticationManager">
		<sec:authentication-provider ref="rememberMeAuthenticationProvider"></sec:authentication-provider>
		<sec:authentication-provider >
			<sec:user-service id="userDetailsService">
				<sec:user authorities="ROLE_USER" name="admin" password="admin" />
			</sec:user-service>
		</sec:authentication-provider>
	</sec:authentication-manager>

</beans:beans>

Third we need to update our login.xhtml to add remember me.

login.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
</h:head>
<h:body>
	<div align="right" style="">
		<h:form	id="loginFormId" prependId="false">
				<div id="loginFieldsPnlId">
					<div id="loginFieldUsrContId">
						<h:outputText id="outTxtUserNameId" value="Username:" name="outTxtUserNameNm"></h:outputText>
						<h:inputText id="userName" required="true" value="#{loginMgmtBean.userName}" requiredMessage="Please enter username"></h:inputText>
						<h:outputLabel id="outLblUserNameId" for="userName" name="outLblUserNameNm"></h:outputLabel>
					</div>
					<div id="loginFieldPassContId">
						<h:outputText id="outTxtPasswordId" value="Password:" name="outTxtPasswordNm"></h:outputText>
						<h:inputSecret id="password"  required="true" value="#{loginMgmtBean.password}" requiredMessage="Please enter password"></h:inputSecret>
						<h:outputLabel id="outLblPasswordId" for="password" name="outLblPasswordNm"></h:outputLabel>
					</div>
					<div id="loginFieldPassContId">
						<h:selectBooleanCheckbox id="rememberMe" value="#{loginMgmtBean.rememberMe}" label="Remember Me">Remember Me</h:selectBooleanCheckbox>
					</div>
				</div>
				<div id="loginBtnPanelId">
					<h:commandButton id="btnLoginId" value="Login" action="#{loginMgmtBean.login}" styleClass="loginPanelBtn"></h:commandButton>
					<h:commandButton id="btnCancelId" value="Cancel" action="#{loginMgmtBean.cancel}" styleClass="loginPanelBtn" immediate="true" update="loginFormId"></h:commandButton>
				</div>
		</h:form>
	</div>
	<div>
		<h:messages></h:messages>
	</div>
</h:body>
</html>

Fourth is to update our LoginBean.java to handle remember me during login and logout.

LoginBean.java

package com.mumz.jsfspringsec.beans;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.RememberMeServices;

/**
 * The Class LoginBean.
 */
@ManagedBean(name = "loginMgmtBean")
@RequestScoped
public class LoginBean {

	/** The user name. */
	private String userName = null;

	/** The password. */
	private String password = null;

	/** The remember me. */
	private String rememberMe = null;

	/** The authentication manager. */
	@ManagedProperty(value = "#{authenticationManager}")
	private AuthenticationManager authenticationManager = null;

	/** The remember me services. */
	@ManagedProperty(value = "#{rememberMeServices}")
	private RememberMeServices rememberMeServices = null;

	@ManagedProperty(value="#{userDetailsService}")
	private UserDetailsService userDetailsService = null;
	/**
	 * Login.
	 *
	 * @return the string
	 */
	public String login() {
		try {
			Authentication result = null;
			if ("TRUE".equalsIgnoreCase(this.getRememberMe())) {
				UserDetails userDetails = userDetailsService.loadUserByUsername(getUserName());
				RememberMeAuthenticationToken rememberMeAuthenticationToken = new RememberMeAuthenticationToken(
						"jsfspring-sec", userDetails,
						userDetails.getAuthorities());
				HttpServletRequest httpServletRequest = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
				HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
				rememberMeServices.loginSuccess(httpServletRequest, httpServletResponse, rememberMeAuthenticationToken);
				result = rememberMeAuthenticationToken;
			} else {
				Authentication request = new UsernamePasswordAuthenticationToken(
						this.getUserName(), this.getPassword());
				result = authenticationManager.authenticate(request);
			}
			SecurityContextHolder.getContext().setAuthentication(result);
		} catch (AuthenticationException e) {
			e.printStackTrace();
		}
		return "Secured";
	}

	/**
	 * Cancel.
	 *
	 * @return the string
	 */
	public String cancel() {
		return null;
	}

	/**
	 * Logout.
	 *
	 * @return the string
	 */
	public String logout() {
		SecurityContextHolder.clearContext();
		/**
		 * Delete Cookies
		 */
		HttpServletRequest httpServletRequest = (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest();
		HttpServletResponse httpServletResponse = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
		Cookie cookie = new Cookie("SPRING_SECURITY_REMEMBER_ME_COOKIE", null);
		cookie.setMaxAge(0);
		cookie.setPath(httpServletRequest.getContextPath().length() > 0 ? httpServletRequest.getContextPath() : "/");
		httpServletResponse.addCookie(cookie);
		return "loggedout";
	}

	/**
	 * Gets the user name.
	 *
	 * @return the user name
	 */
	public String getUserName() {
		return userName;
	}

	/**
	 * Sets the user name.
	 *
	 * @param userName
	 *            the new user name
	 */
	public void setUserName(String userName) {
		this.userName = userName;
	}

	/**
	 * Gets the password.
	 *
	 * @return the password
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * Sets the password.
	 *
	 * @param password
	 *            the new password
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * Gets the remember me.
	 *
	 * @return the remember me
	 */
	public String getRememberMe() {
		return rememberMe;
	}

	/**
	 * Sets the remember me.
	 *
	 * @param rememberMe
	 *            the new remember me
	 */
	public void setRememberMe(String rememberMe) {
		this.rememberMe = rememberMe;
	}

	/**
	 * Gets the authentication manager.
	 *
	 * @return the authentication manager
	 */
	public AuthenticationManager getAuthenticationManager() {
		return authenticationManager;
	}

	/**
	 * Sets the authentication manager.
	 *
	 * @param authenticationManager
	 *            the new authentication manager
	 */
	public void setAuthenticationManager(
			AuthenticationManager authenticationManager) {
		this.authenticationManager = authenticationManager;
	}

	/**
	 * Gets the remember me services.
	 *
	 * @return the remember me services
	 */
	public RememberMeServices getRememberMeServices() {
		return rememberMeServices;
	}

	/**
	 * Sets the remember me services.
	 *
	 * @param rememberMeServices
	 *            the new remember me services
	 */
	public void setRememberMeServices(RememberMeServices rememberMeServices) {
		this.rememberMeServices = rememberMeServices;
	}

	public UserDetailsService getUserDetailsService() {
		return userDetailsService;
	}

	public void setUserDetailsService(UserDetailsService userDetailsService) {
		this.userDetailsService = userDetailsService;
	}
}

That’s it our remember me functionality is now implemented. Deploy, Run and let me know your comments.

There are 16 comments

  1. Allan Wood

    I implemented my own userDetailService, where in your code should I put the reference to my userdetail service, I usually put it in the tag. But since you put a ref attribute into the tag, I couldn’t put my user-service-ref. What do I do?

    Thanks in advance.

  2. Victor

    I have next error after timeout “viewId:/pages/common/login.jsf – No se pudo restablecer la vista /pages/common/login.jsf.”. what’s the problem

    1. hcubyc

      There’s a password validation. Method login() catches
      AuthenticationException, but it doesn’t do much when it happens. If
      you want, you can return something in catch clause for instance
      “error” or “loginFailed” or do anything what do you really
      want.

    2. Spyros Non Serviam

      I have noticed this also but I found a solution. I moved
      the else part of the LoginBean before the if and it seems to be
      working properly.

  3. Rachid

    Thanks for this very nice topic,
    I used your code to create my own login, every thing is just right unless Remeber Me.
    It doesn’t work, i mean when i re-open the browser, i have to re login.

    Any ideas please ?

  4. Ramz

    I am getting a null pointer exception as there is no UserDetailsService refrence in bean configuration for the LoginBean. When i placed the below code in bean configuration, it worked. May be you should add it.

    However it is there under “rememberMeServices” bean id. Not sure if it is getting initialized.

    1. sreejith

      Hi Ramz ,

      Please let me know how you fixed this null pointer exception in UserDetailsService reference .
      I am struck with this , getting this exception .

      Please help .

  5. Amy

    I tried to integrate the above code in my project and it does work. I am getting an error

    “Context initialization failed: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘rememberMeServices’ defined in ServletContext resource [/WEB-INF/applicationContext.xml]: Cannot resolve reference to bean ‘userDetailsService’ while setting bean property ‘userDetailsService’; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘userDetailsService’ is defined”

    Please let me know how to fix it. userDetailsService is a reference to an interface and it does not allow to create a bean for the same.

  6. Alex

    I don’t think this works with Spring 4.0.1.

    Cannot resolve reference to bean ‘userDetailsService’ while setting bean property ‘userDetailsService’; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘userDetailsService’ is defined

    I use Spring 4.0.1, NetBeans 8.0.1 and deploy to WildFly 8.1.0

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s