Today we will add remember-me functionality to our project.
We will enhance our code from earlier revision, namely:
- Integrating Spring Security 3.1 and JSF 2.0
- Spring Security 3.1 and JSF 2.0 Custom Form
- 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.
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.
If you want to inject your own implementation you can take a look at this post https://mprabhat.wordpress.com/2012/07/18/357/
Hope this helps
Dude! Thank you! You helped me a lot, whoohoo!
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
I don’t get it, why there’s no password validation when the “Remember Me” check-box is checked?
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.
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.
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 ?
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.
I have the same problem? have you found any solution?
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 .
Hi, did you managed to resolve this issue, I’m stuck.
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.
Hi, did you managed to resolve this issue (userDetailsService NPE), I’m stuck.
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
Yeah may be, I am working on getting all these samples ported to Spring 4 and that too with complete Maven Project. Should be there soon before Christmas I would say