Add RememberMe Authentication With Spring Security
I mentioned in my post Add Social Login to Jiwhiz Blog that the RememberMe function was not working with Spring Social Security. Well, it is because the application is not authenticating the user by username and password now, and is totally depending on social websites (like Google, Facebook and Twitter) to do the job. The default Spring Security configuration cannot handle this situation. Spring Security might be the most complicated software among all Spring Portfolio projects. There are about 10 filters needing to be set up correctly to get a very simple Web application to work with security. To simplify application development, Spring Security provides namespace configuration since version 2.0 to automatically set up all the necessary components together, so developers don’t need to figure out the details. It works very well for most of the Web applications, unless your application is different from a traditional one.
After I changed my website login process from username password authentication to Spring Social Security without a password, the old configuration for remember-me didn’t work anymore. The Spring Security Reference document has very few explanations about Remember-Me Authentication, so I bought the book Spring Security 3.1 written by Rob Winch, the project lead of Spring Security. The book has an entire chapter talking about Remember-Me Services, and it helped me a lot to understand how remember-me works in Spring Security. After reading the book, I feel it is much easier to read the Spring Security source code, and reading the source code is always enjoyable.
Since I’m not storing passwords for users’ accounts, the default TokenBasedRememberMeServices cannot work with my application, and I don’t want to create my own RememberMeServices – too much work. Fortunately, there is another Persistent Token Approach, that is to store tokens into the database and compare tokens inside cookies. All I need is to customize PersistentTokenBasedRememberMeServices in my application with PersistentTokenRepository to store the tokens. Spring Security provides a JDBC implementation of PersistentTokenRepository, and I found it is trivial to write my own implementation for MongoDB after reading the source code.
The first step is to store data of PersistentRememberMeToken to MongoDB. I need to add a domain entity class for it:
@Document(collection = 'RememberMeToken') public class RememberMeToken extends BaseEntity{ private String username; @Indexed private String series; private String tokenValue; private Date date; ... // getter/setter omitted public RememberMeToken(){ } public RememberMeToken(PersistentRememberMeToken token){ this.series = token.getSeries(); this.username = token.getUsername(); this.tokenValue = token.getTokenValue(); this.date = token.getDate(); } }
Next, use Spring Data to add a repository for the entity:
public interface RememberMeTokenRepository extends MongoRepository<RememberMeToken, String>{ RememberMeToken findBySeries(String series); List<RememberMeToken> findByUsername(String username); }
Then the only relatively heavy coding is to implement PersistentTokenRepository for MongoDB:
public class MongoPersistentTokenRepositoryImpl implements PersistentTokenRepository { private final RememberMeTokenRepository rememberMeTokenRepository; public MongoPersistentTokenRepositoryImpl(RememberMeTokenRepository rememberMeTokenRepository){ this.rememberMeTokenRepository = rememberMeTokenRepository; } @Override public void createNewToken(PersistentRememberMeToken token) { RememberMeToken newToken = new RememberMeToken(token); this.rememberMeTokenRepository.save(newToken); } @Override public void updateToken(String series, String tokenValue, Date lastUsed) { RememberMeToken token = this.rememberMeTokenRepository.findBySeries(series); if (token != null){ token.setTokenValue(tokenValue); token.setDate(lastUsed); this.rememberMeTokenRepository.save(token); } } @Override public PersistentRememberMeToken getTokenForSeries(String seriesId) { RememberMeToken token = this.rememberMeTokenRepository.findBySeries(seriesId); return new PersistentRememberMeToken(token.getUsername(), token.getSeries(), token.getTokenValue(), token.getDate()); } @Override public void removeUserTokens(String username) { List<RememberMeToken> tokens = this.rememberMeTokenRepository.findByUsername(username); this.rememberMeTokenRepository.delete(tokens); } }
The rest of the work is all configuration. I need to wire them together in Java config class:
@Configuration public class SocialAndSecurityConfig { @Inject private Environment environment; @Inject private AccountService accountService; @Inject private AuthenticationManager authenticationManager; @Inject private RememberMeTokenRepository rememberMeTokenRepository; ... @Bean public RememberMeServices rememberMeServices(){ PersistentTokenBasedRememberMeServices rememberMeServices = new PersistentTokenBasedRememberMeServices( environment.getProperty('application.key'), accountService, persistentTokenRepository()); rememberMeServices.setAlwaysRemember(true); return rememberMeServices; } @Bean public RememberMeAuthenticationProvider rememberMeAuthenticationProvider(){ RememberMeAuthenticationProvider rememberMeAuthenticationProvider = new RememberMeAuthenticationProvider(environment.getProperty('application.key')); return rememberMeAuthenticationProvider; } @Bean public PersistentTokenRepository persistentTokenRepository() { return new MongoPersistentTokenRepositoryImpl(rememberMeTokenRepository); } }
The last step is to add the remember-me service to security xml config file, which is the last piece of xml config and we cannot eliminate it now. (Update: a new project Spring Security Java Config will replace xml configuration with Java config in Spring Security.)
<?xml version='1.0' encoding='UTF-8'?> <beans:beans xmlns='http://www.springframework.org/schema/security' xmlns:beans='http://www.springframework.org/schema/beans' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' 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'> <http use-expressions='true' entry-point-ref='socialAuthenticationEntryPoint'> <custom-filter position='PRE_AUTH_FILTER' ref='socialAuthenticationFilter' /> <logout logout-url='/signout' delete-cookies='JSESSIONID' /> <remember-me services-ref='rememberMeServices' /> <!-- Configure these elements to secure URIs in your application --> <intercept-url pattern='/favicon.ico' access='permitAll' /> <intercept-url pattern='/robots.txt' access='permitAll' /> <intercept-url pattern='/resources/**' access='permitAll' /> <intercept-url pattern='/signin' access='permitAll' requires-channel='#{environment['application.secureChannel']}' /> <intercept-url pattern='/signin/*' access='permitAll' requires-channel='#{environment['application.secureChannel']}' /> <intercept-url pattern='/presentation/**' access='hasRole('ROLE_USER')' requires-channel='#{environment['application.secureChannel']}' /> <intercept-url pattern='/myAccount/**' access='hasRole('ROLE_USER')' requires-channel='#{environment['application.secureChannel']}' /> <intercept-url pattern='/myPost/**' access='hasRole('ROLE_AUTHOR')' requires-channel='#{environment['application.secureChannel']}' /> <intercept-url pattern='/admin/**' access='hasRole('ROLE_ADMIN')' requires-channel='#{environment['application.secureChannel']}' /> <intercept-url pattern='/**' access='permitAll' /> </http> <authentication-manager alias='authenticationManager'> <authentication-provider ref='socialAuthenticationProvider' /> <authentication-provider ref='rememberMeAuthenticationProvider' /> </authentication-manager> </beans:beans>
That’s all for adding Remember-Me Authentication to my blog application. Now you can login through Google/Facebook/Twitter to my website, and the website will consistently remember you for the next two weeks.
Reference: Add RememberMe Authentication With Spring Security from our JCG partner Yuan Ji at the Jiwhiz blog.
Hi Yuan,
Thank you for this article. Can you please provide an example project for facebook login with remember me services? In my project, I have integrated Facebook Javascript SDK and I get the facebook id and the facebook oauth token which I then pass it to a controller. In this setup, I am not sure how does the authentication manger fits in. Is this the right setup? I wanted to know how you have implemented “socialAuthenticationProvider”, “socialAuthenticationEntryPoint” and “socialAuthenticationFilter”.
It would be great if you could provide an example project with the same.
Thanks!
Not working
Where can I find the complete working implementation of this ?