Enterprise Java

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.

Yuan Ji

Yuan is a passionate Java programmer and open source evangelist. He is eager to learn new technologies and loves clean and beautiful application design. He lives in Edmonton, Canada as an independent consultant and contractor.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Poonam
Poonam
11 years ago

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!

John Simth
John Simth
11 years ago

Not working

Manas
Manas
9 years ago

Where can I find the complete working implementation of this ?

Back to top button