Adding Social Sign In to a Spring MVC Web Application: Configuration
In the good old days users logged in by using the combination of username and password. Although nowadays some people still prefer the traditional way, a growing number of users want to sign in by using their social media accounts.
This is a what makes Spring Social (and its sub projects) an useful addition to the Spring project portfolio. However, integrating Spring Social with Spring Security has been a bit cumbersome.
Spring Social 1.1.0 changes all this. It provides seamless integration with Spring Security, and the Java configuration support of Spring Security makes the configuration feel like a walk in the park.
You don’t have to take my word for it. Keep on reading and you will learn how this is done.
The requirements of our solution are the following:
- It must be possible to create an user account by using a normal registration form.
- It must be possible to create an user account by using a social sign in.
- It must be possible to login by using username and password.
- It must be possible to login by using a SaaS API provider.
- The application must support Facebook and Twitter.
- The application must use “regular” Spring MVC controllers (no REST).
Let’s start by taking a look at the prerequisites of this tutorial.
Prerequisites
This tutorial assumes that you have already created the Facebook and Twitter application used by the example application. You can create these applications by following these links:
If you don’t know how to do this, you can check out the following links:
- Facebook Developers – Creating an App Details Page (Select “website with Facebook login” when you are asked how your application integrates with FB).
- How to Create a Twitter App in 8 Easy Steps (Enable the “allow this application to be used to Sign in with Twitter” checkbox).
Let’s move on and find out how we can get the required dependencies with Maven.
Getting the Required Dependencies with Maven
The first thing that we have to do is to get the required dependencies with Maven. We can do this by declaring the following dependencies in our POM file:
- Spring Security (version 3.2.0.RC1).
- The core module contains core authentication and and access control components.
- The config module contains the code used to parse XML configuration files using the Spring Security XML namespace.
- The taglibs module contains the Spring Security JPS tag libraries.
- The web module contains filters and all other code related to web security.
- Apache HttpClient (version 4.2.5). Apache HttpClient is an optional dependency (but recommended) dependency of Spring Social. If it is present, Spring Social will use it as a HTTP client. If not, Spring social will use the standard Java SE components.
- Spring Social (version 1.1.0.BUILD-SNAPSHOT).
- The core module contains the connect framework and provides support for OAuth clients.
- The security module integrates Spring Security with Spring Social. It delegates the authentication concerns typically taken care by Spring Security to service providers by using Spring Social.
- The web module contains components which handle the authentication handshake between our web application and the service provider.
- Spring Social Facebook (version 1.1.0.BUILD-SNAPSHOT) is an extension to Spring Social and it provides Facebook integration.
- Spring Social Twitter (version 1.1.0.BUILD-SNAPSHOT) is an extension to Social Social which provides Twitter integration.
The relevant part of the pom.xml file looks as follows:
<!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>3.2.0.RC1</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>3.2.0.RC1</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>3.2.0.RC1</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>3.2.0.RC1</version> </dependency> <!-- Use Apache HttpClient as HTTP Client --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.2.5</version> </dependency> <!-- Spring Social --> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-core</artifactId> <version>1.1.0.BUILD-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-security</artifactId> <version>1.1.0.BUILD-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-web</artifactId> <version>1.1.0.BUILD-SNAPSHOT</version> </dependency> <!-- Spring Social Facebook --> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-facebook</artifactId> <version>1.1.0.BUILD-SNAPSHOT</version> </dependency> <!-- Spring Social Twitter --> <dependency> <groupId>org.springframework.social</groupId> <artifactId>spring-social-twitter</artifactId> <version>1.1.0.BUILD-SNAPSHOT</version> </dependency>
Note: Our application has other dependencies as well. For example, it uses Spring Framework 3.2.4.RELEASE, Spring Data JPA 1.3.4, and Hibernate 4.2.4.Final. These dependencies are left out from the dependency listing for the sake of clarity. You can get the full list of dependencies from Github.
You might also want to read the following documents which give you more information about the dependencies of the frameworks discussed in this blog post (Spring Security and Spring Social):
- Spring Security Reference Manual: 1.4 Getting Spring Security
- Spring Social Reference Manual: 1.3 How to get
- Spring Social Facebook Reference Manual: 1.2 How to get
- Spring Social Twitter Reference Manual: 1.2 How to get
Next we have to create a properties file for the configuration properties of our application. Let’s find out how this is done.
Creating the Properties File
We can create the properties file by following these steps:
- Create a file called application.properties and ensure that it is found from the classpath.
- Configure the database connection.
- Configure Hibernate.
- Add the Facebook application id and application secret to the properties file.
- Add the Twitter consumer key and consumer secret to the properties file.
The contents of the application.properties file looks as follows:
#Database Configuration db.driver=com.mysql.jdbc.Driver db.url=jdbc:mysql://localhost:3306/socialtwitter db.username=socialtwitter db.password=password #Hibernate Configuration hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect hibernate.format_sql=true hibernate.hbm2ddl.auto=validate hibernate.ejb.naming_strategy=org.hibernate.cfg.ImprovedNamingStrategy hibernate.show_sql=false #Facebook facebook.app.id=foo facebook.app.secret=bar #Twitter twitter.consumer.key=foo twitter.consumer.secret=bar
Before we can configure our application, we have to create a few common components. Let’s find out what these components are, and how we can create them.
Creating the Common Components
We have to create three components which are used during the authentication process. These components are:
- We have create a class which contains the user details of an authenticated user.
- We have to create a class which implements the UserDetailsService interface. This class is used to load user information when the user uses form login.
- We have to create a class which implements the SocialUserDetailsService interface. This class is used to load user information when the user uses social sign in.
Let’s move on and find out how we can implement these classes.
Creating the User Details Class
We have to take the following requirements into account when we are creating the class which contains the user details of the authenticated user:
- The class which stores the user details of a user who uses form login must implement the UserDetails interface.
- The class which stores the user details of a user who uses social sign in must implement the SocialUserDetails interface.
Spring Social has a SocialUser class which fulfils both of these requirements. However, often we want to add application specific information to our user details class.
We can do this by following these steps:
- Create the user details class.
- Extend the SocialUser class.
- Add application specific fields to the created class. The application specific fields of our example application are: id, firstName, lastName, role, and socialSignInProvider.
- Create a constructor which takes the username, password and a collection of granted authorities as parameters. Pass these parameters forward to the constructor of the SocialUser class.
- Create getters for application specific fields.
- Add an inner builder class which is used to build new ExampleUserDetails objects.
The source code of our user details class looks as follows:
import org.apache.commons.lang3.builder.ToStringBuilder; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.social.security.SocialUser; import java.util.Collection; import java.util.HashSet; import java.util.Set; public class ExampleUserDetails extends SocialUser { private Long id; private String firstName; private String lastName; private Role role; private SocialMediaService socialSignInProvider; public ExampleUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) { super(username, password, authorities); } //Getters are omitted for the sake of clarity. public static class Builder { private Long id; private String username; private String firstName; private String lastName; private String password; private Role role; private SocialMediaService socialSignInProvider; private Set<GrantedAuthority> authorities; public Builder() { this.authorities = new HashSet<>(); } public Builder firstName(String firstName) { this.firstName = firstName; return this; } public Builder id(Long id) { this.id = id; return this; } public Builder lastName(String lastName) { this.lastName = lastName; return this; } public Builder password(String password) { if (password == null) { password = "SocialUser"; } this.password = password; return this; } public Builder role(Role role) { this.role = role; SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.toString()); this.authorities.add(authority); return this; } public Builder socialSignInProvider(SocialMediaService socialSignInProvider) { this.socialSignInProvider = socialSignInProvider; return this; } public Builder username(String username) { this.username = username; return this; } public ExampleUserDetails build() { ExampleUserDetails user = new ExampleUserDetails(username, password, authorities); user.id = id; user.firstName = firstName; user.lastName = lastName; user.role = role; user.socialSignInProvider = socialSignInProvider; return user; } } }
The Role is a simple enum which specifies the “legal” user roles of our example application. Its source code looks as follows:
public enum Role { ROLE_USER }
The SocialMediaService is an enum which identifies the SaaS API provider which was used when user created an user account to our example application. Its source code looks as follows:
public enum SocialMediaService { FACEBOOK, TWITTER }
Implementing the UserDetailsService interface
We can create our own implementation of the UserDetailsService interface by following these steps:
- Create a class which implements the UserDetailsService interface.
- Add a UserRepository field to created class.
- Create a constructor which takes a UserRepository as a constructor argument and annotate the constructor with the @Autowired annotation.
- Implement the loadUserByUsername(String username) method of the UserDetailsService interface. The implementation of this method consists of following steps:
- Get the user by calling the findByEmail() method of the UserRepository interface. This method returns the user whose email matches with the username given as a method parameter.
- If the user is not found, throw a new UsernameNotFoundException.
- Create a new ExampleUserDetails object.
- Return the created object.
The source code of the RepositoryUserDetailsService class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; public class RepositoryUserDetailsService implements UserDetailsService { private UserRepository repository; @Autowired public RepositoryUserDetailsService(UserRepository repository) { this.repository = repository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = repository.findByEmail(username); if (user == null) { throw new UsernameNotFoundException("No user found with username: " + username); } ExampleUserDetails principal = ExampleUserDetails.getBuilder() .firstName(user.getFirstName()) .id(user.getId()) .lastName(user.getLastName()) .password(user.getPassword()) .role(user.getRole()) .socialSignInProvider(user.getSignInProvider()) .username(user.getEmail()) .build(); return principal; } }
The UserRepository is a simple Spring Data JPA repository, and its source code looks as follows:
import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { public User findByEmail(String email); }
The User is the only entity of our example application, and it contains the information of a user who has created user account to our example application. The relevant part of its source code looks as follows:
import javax.persistence.*; @Entity @Table(name = "users") public class User extends BaseEntity<Long> { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; @Column(name = "email", length = 100, nullable = false, unique = true) private String email; @Column(name = "first_name", length = 100,nullable = false) private String firstName; @Column(name = "last_name", length = 100, nullable = false) private String lastName; @Column(name = "password", length = 255) private String password; @Enumerated(EnumType.STRING) @Column(name = "role", length = 20, nullable = false) private Role role; @Enumerated(EnumType.STRING) @Column(name = "sign_in_provider", length = 20) private SocialMediaService signInProvider; public User() { } //Getters and other methods are omitted for the sake of clarity. }
Implementing the SocialUserDetailsService interface
We can implement the SocialUserDetailsService interface by following these steps:
- Create a class which implements the SocialUserDetailsService.
- Add a UserDetailsService field to the created class.
- Create a constructor which takes a UserDetailsService object as a constructor parameter, and annotate the constructor with the @Autowired annotation.
- Implement the loadUserByUserId(String userId) method of the SocialUserDetailsInterface.
- Get the correct UserDetails object by calling the loadUserByUsername() method and pass the user id as a method parameter. We can do this because our application uses the username of the user as the user id.
- Cast the returned object to SocialUserDetails object and return it.
The source code of the SimpleSocialUserDetailsService class looks as follows:
import org.springframework.dao.DataAccessException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.social.security.SocialUser; import org.springframework.social.security.SocialUserDetails; import org.springframework.social.security.SocialUserDetailsService; public class SimpleSocialUserDetailsService implements SocialUserDetailsService { private UserDetailsService userDetailsService; public SimpleSocialUserDetailsService(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Override public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException, DataAccessException { UserDetails userDetails = userDetailsService.loadUserByUsername(userId); return (SocialUserDetails) userDetails; } }
That is all. We are now ready to configure the application context of our application. Let’s find out how we can do that.
Configuring the Application Context
This section describes how we can configure the application context of our example application by using Java configuration. The application context configuration has been divided into multiple configuration classes by following these guidelines:
- Each configuration class contains configuration which is associated with a specific part of our example application. This make it easy to find out the relevant configuration if we have to check something out or change something a few months (or years) after we created the initial configuration.
- The configuration has been divided in a way which makes it easy to write unit tests for the web layer by using Spring Test MVC. We will talk more about this in the third part of this tutorial where we will write unit tests for the web layer of our application.
- The configuration makes it easy remove dependencies to external resources when we are writing integration tests for our application. We will talk more about this in the fourth part of this tutorial which describes how we can write integration tests for our application.
Note: If you want to use XML configuration, you can take look at the example application of this blog post which has a working XML configuration as well (no web.xml though).
Let’s start by configuring the persistence layer of our application.
Configuring the Persistence Layer
The persistence layer of our application stores the user account information and provides a way to access this information. This important for two reasons:
- We can provide a way to sign in by using username and password.
- We can store application specific information and link this information to the user who uses social sign in.
Let’s find out how we can configure it by using both Java configuration class.
Note: The persistence layer of example application uses Spring Data JPA 1.3.4. I will keep this section as thin as possible. If you want to learn more about Spring Data JPA, you can read my Spring Data JPA tutorial. I have also written a book about Spring Data which should help you to get started in no time.
We can configure our persistence layer by following these steps:
- Create the configuration class and annotate the created class with the @Configuration annotation.
- Annotate the class with the @EnableJpaRepositories annotation and set the base package of our Spring Data JPA repositories.
- Enable the Spring transaction management by annotating the configuration class with the @EnableTransactionManagement annotation.
- Add an Environment field to the class and annotate the field with the @Autowired annotation. We don’t need to configure the properties file by using the @PropertySource annotation because it is already configured in the “parent” application context configuration class.
- Configure the data source bean. This bean provides database connections to the entity manager but it has also another purpose. It is used by Spring Social when it persists connections to the database and loads them from the database.
- Configure the transaction manager bean.
- Configure the entity manager factory bean.
The source code of the PersistenceContext class looks as follows:
import com.jolbox.bonecp.BoneCPDataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import org.springframework.transaction.annotation.EnableTransactionManagement; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.Properties; @Configuration @EnableJpaRepositories(basePackages = { "net.petrikainulainen.spring.social.signinmvc.user.repository" }) @EnableTransactionManagement public class PersistenceContext { @Resource private Environment env; @Bean public DataSource dataSource() { BoneCPDataSource dataSource = new BoneCPDataSource(); dataSource.setDriverClass(env.getRequiredProperty("db.driver")); dataSource.setJdbcUrl(env.getRequiredProperty("db.url")); dataSource.setUsername(env.getRequiredProperty("db.username")); dataSource.setPassword(env.getRequiredProperty("db.password")); return dataSource; } @Bean public JpaTransactionManager transactionManager() { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(entityManagerFactory().getObject()); return transactionManager; } @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean(); entityManagerFactoryBean.setDataSource(dataSource()); entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); entityManagerFactoryBean.setPackagesToScan({ "net.petrikainulainen.spring.social.signinmvc.common.model", "net.petrikainulainen.spring.social.signinmvc.user.model" }); Properties jpaProperties = new Properties(); jpaProperties.put("hibernate.dialect", env.getRequiredProperty("hibernate.dialect")); jpaProperties.put("hibernate.format_sql", env.getRequiredProperty("hibernate.format_sql")); jpaProperties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty("hibernate.hbm2ddl.auto")); jpaProperties.put("hibernate.ejb.naming_strategy", env.getRequiredProperty("hibernate.ejb.naming_strategy")); jpaProperties.put("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql")); entityManagerFactoryBean.setJpaProperties(jpaProperties); return entityManagerFactoryBean; } }
Let’s move on and find out how we can create the security configuration for our application.
Configuring Spring Security
Spring Security provides authentication mechanism for users who uses either form login or social sign in, and it is also responsible of authorization.
We can configure Spring Security by following these steps:
- Create the configuration class and annotate the created class with the @Configuration annotation.
- Annotate the class with the @EnableWebSecurity annotation. This makes it possible to configure Spring Security by implementing the WebSecurityConfigurer interface.
- Ensure that our configuration class extends the WebSecurityConfigurerAdapter class which is a base class for creating WebSecurityConfigurer instances. After we have done this, we can customize the security configuration by overriding methods.
- Add an ApplicationContext field to the configuration class and annotate the field with the @Autowired annotation.
- Add an UserRepository field to the configuration and annotate the field with the @Autowired annotation.
- Override the configure(WebSecurity web) method of the WebSecurityConfigurerAdapter class. Ensure that Spring Security ignores requests made to static resources such as CSS and Javascript files.
- Override the configure(HttpSecurity http) method of the WebSecurityConfigurerAdapter class and implement it by following these steps:
- Configure form login by following these steps:
- Set the login page url to ‘/login’.
- Set the url which processes login form submissions to ‘/login/authenticate’.
- Set the login failure url to ‘/login?error=bad_credentials’.
- Configure the logout function by following these steps:
- Ensure that a cookie called JSESSIONID is deleted after logout.
- Set the logout url to ‘/logout’.
- Set the logout success url to ‘/login’.
- Configure url based authorization. The main point of this phase is to ensure that anonymous users can access all urls which are related to the sign in / registration process, and protect the rest of our application from anonymous users.
- Add the SocialAuthenticationFilter to the Spring Security filter chain. We can do this by creating a new SpringSocialConfigurer object and ensuring that this object is used when Spring Security is configured.
- Set the value of the ApplicationContext field as an object which is shared by all SecurityConfigurer instances.
- Configure form login by following these steps:
- Configure the PasswordEncoder bean which is used to hash the password of the user (if the user uses form registration and login). We can do this by creating a new BCryptPasswordEncoder object and returning the created object.
- Configure the UserDetailsService bean. We can do this by creating a new RepositoryUserDetailsService object and passing the UserRepository as a constructor argument.
- Override the registerAuthentication(AuthenticationManagerBuilder auth) method of the WebSecurityConfigurerAdapter class. We use this method for configuring authentication requests if the user uses form login. Implement this method by following these steps:
- Pass the UserDetailsService bean to the AuthenticationManagerBuilder object given as a method parameter.
- Pass the PasswordEncoder bean to the AuthenticationManagerBuilder object given as a method parameter.
- Configure the SocialUserDetailsService bean. We can do this by creating a new SimpleSocialUserDetailsService object and passing the UserDetailsService bean as a constructor argument. This bean loads the user specific data when social sign in is used.
The source code of our application context configuration class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.social.security.SocialUserDetailsService; import org.springframework.social.security.SpringSocialConfigurer; @Configuration @EnableWebSecurity public class SecurityContext extends WebSecurityConfigurerAdapter { @Autowired private ApplicationContext context; @Autowired private UserRepository userRepository; @Override public void configure(WebSecurity web) throws Exception { web //Spring Security ignores request to static resources such as CSS or JS files. .ignoring() .antMatchers("/static/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http //Configures form login .formLogin() .loginPage("/login") .loginProcessingUrl("/login/authenticate") .failureUrl("/login?error=bad_credentials") //Configures the logout function .and() .logout() .deleteCookies("JSESSIONID") .logoutUrl("/logout") .logoutSuccessUrl("/login") //Configures url based authorization .and() .authorizeRequests() //Anyone can access the urls .antMatchers( "/auth/**", "/login", "/signin/**", "/signup/**", "/user/register/**" ).permitAll() //The rest of the our application is protected. .antMatchers("/**").hasRole("USER") //Adds the SocialAuthenticationFilter to Spring Security's filter chain. .and() .apply(new SpringSocialConfigurer()) .and() .setSharedObject(ApplicationContext.class, context); } @Override protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception { auth .userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(10); } @Bean public SocialUserDetailsService socialUserDetailsService() { return new SimpleSocialUserDetailsService(userDetailsService()); } @Bean public UserDetailsService userDetailsService() { return new RepositoryUserDetailsService(userRepository); } }
Let’s move on and find out how we can configure Spring Social.
Configuring Spring Social
Spring Social provides integrations with SaaS API providers such as Facebook and Twitter. We can configure Spring Social by following these steps:
- Create the application context configuration class which implements the SocialConfigurer interface and annotate the created class with the @Configuration annotation. The SocialConfigurer interface declares callback methods which can be used to configure Spring Social.
- Annotate the class with the @EnableSocial annotation. This enables Spring Social and imports the SocialConfiguration configuration class.
- Annotate the class with @Profile annotation and set the string ‘application’ as its value. This ensures that this application context configuration class is used only when the active Spring profile is ‘application’. This is important because it gives us the possibility to write integration tests for our application without relying on Facebook or Twitter.
- Add a DataSource field to the configuration class and annotate the field with the @Autowired annotation.
- Add the addConnectionFactories() method of the SocialConfigurer interface to the created configuration class. This method takes two method parameters which are described in the following:
- The first parameter is a ConnectionFactoryConfigurer object which can be used to register connection factories.
- The second parameter is an Environment object which represents the environment in which our example application is running.
- Implement the addConnectionFactories() method by following these steps:
- Create a new TwitterConnectionFactory object, and pass the consumer key and the consumer secret as constructor arguments.
- Register the created TwitterConnectionFactory object by calling the addConnectionFactory() method of the ConnectionFactoryConfigurer interface. Pass the created TwitterConnectionFactory object as a method parameter.
- Create a new FacebookConnectionFactory object, and pass the application id and the application secret as constructor arguments.
- Register the created FacebookConnectionFactory object by calling the addConnectionFactory method of the ConnectionFactoryConfigurer interface. Pass the created FacebookConnectionFactory object as a method parameter.
- Add the getUserIdSource() method of the SocialConfigurer interface to the created class. The UserIdSource object returned by this method is responsible of determining the correct account id of the user. Because our example application uses the username of the user as an account id, we have to implement this method by returning a new AuthenticationNameUserIdSource object.
- Add the getUsersConnectionRepository() method of the SocialConfigurer interface to the created class. This method takes a ConnectionFactoryLocator object as a method parameter and returns a UsersConnectionRepository object.
- Implement the getUsersConnectionRepository() method by following these steps:
- Create a new JdbcUsersConnectionRepository object and pass the following objects as constructor arguments:
- The first argument is a DataSource object. We pass the value of the dataSource field as the first method parameter.
- The second argument is a ConnectionFactoryLocator object. We pass the value of the connectionFactoryLocator method parameter as the second method parameter.
- The third parameter is a TextEncryptor object which encrypts the authorization details of the connection established between a SaaS API provider and our application. We create this object by calling the noOpText() method of the Encryptors class. This means that our example application stores these details as plaintext. This is handy during the development phase but we should not use it in production.
- Return the created object.
- Create a new JdbcUsersConnectionRepository object and pass the following objects as constructor arguments:
- Configure the ConnectController bean. The method which configures this bean has two parameters. The first parameter is the ConnectionFactoryLocator bean. The second parameter is the used ConnectionRepository bean. Pass these parameters as constructor arguments when you are creating a new ConnectController object.
The source code of our configuration class looks as follows:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; import org.springframework.core.env.Environment; import org.springframework.security.crypto.encrypt.Encryptors; import org.springframework.social.UserIdSource; import org.springframework.social.config.annotation.ConnectionFactoryConfigurer; import org.springframework.social.config.annotation.EnableSocial; import org.springframework.social.config.annotation.SocialConfigurer; import org.springframework.social.connect.ConnectionFactoryLocator; import org.springframework.social.connect.ConnectionRepository; import org.springframework.social.connect.UsersConnectionRepository; import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository; import org.springframework.social.connect.web.ConnectController; import org.springframework.social.facebook.connect.FacebookConnectionFactory; import org.springframework.social.security.AuthenticationNameUserIdSource; import org.springframework.social.twitter.connect.TwitterConnectionFactory; import javax.sql.DataSource; @Configuration @EnableSocial @Profile("application") public class SocialContext implements SocialConfigurer { @Autowired private DataSource dataSource; @Override public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) { cfConfig.addConnectionFactory(new TwitterConnectionFactory( env.getProperty("twitter.consumer.key"), env.getProperty("twitter.consumer.secret") )); cfConfig.addConnectionFactory(new FacebookConnectionFactory( env.getProperty("facebook.app.id"), env.getProperty("facebook.app.secret") )); } @Override public UserIdSource getUserIdSource() { return new AuthenticationNameUserIdSource(); } @Override public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { return new JdbcUsersConnectionRepository( dataSource, connectionFactoryLocator, Encryptors.noOpText() ); } @Bean public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator, ConnectionRepository connectionRepository) { return new ConnectController(connectionFactoryLocator, connectionRepository); } }
Our next step is to configure the web layer of our application. Let’s get to work.
Configuring the Web Layer
We can configure the web layer of our application by following these steps:
- Create the configuration class by following these steps:
- Extend the WebMvcConfigurerAdapter class.
- Annotate the created class with the @Configuration annotation.
- Ensure that all controller classes are found by annotating the class with the @ComponentScan annotation and setting the base packages of our controllers.
- Enable the annotation driven web mvc by annotating the class with the @EnableWebMvc annotation.
- Ensure that static resources are served by container’s default servlet.
- Configure the static resources by overriding the addResourceHandlers() method of the WebMvcConfigurerAdapter class.
- Ensure that requests made to static resources are delegated forward to the container’s default servlet. This is done by overriding the configureDefaultServletHandling() method of the WebMvcConfigurerAdapter class.
- Configure the exception resolver bean.
- Configure the ViewResolver bean.
The source code of the WebAppContext class looks as follows:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; import java.util.Properties; @Configuration @ComponentScan(basePackages = { "net.petrikainulainen.spring.social.signinmvc.common.controller", "net.petrikainulainen.spring.social.signinmvc.security.controller", "net.petrikainulainen.spring.social.signinmvc.user.controller" }) @EnableWebMvc public class WebAppContext extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } @Bean public SimpleMappingExceptionResolver exceptionResolver() { SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver(); Properties exceptionMappings = new Properties(); exceptionMappings.put("java.lang.Exception", "error/error"); exceptionMappings.put("java.lang.RuntimeException", "error/error"); exceptionResolver.setExceptionMappings(exceptionMappings); Properties statusCodes = new Properties(); statusCodes.put("error/404", "404"); statusCodes.put("error/error", "500"); exceptionResolver.setStatusCodes(statusCodes); return exceptionResolver; } @Bean public ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/jsp/"); viewResolver.setSuffix(".jsp"); return viewResolver; } }
Let’s find out how we can tie this all together and create a “parent” application context configuration class for our application.
Tieing It All Together
The last application context configuration class has three responsibilities:
- It configures general components used throughout our example application.
- It ensures that the service classes of our application are found during the classpath scan.
- It is the root application context configuration class of our application.
We can create this configuration class by following these steps:
- Create the configuration class and annotate the created class with the @Configuration annotation.
- Ensure that our service classes are found during the component scan by annotating the class with @ComponentScan annotation and setting the base package of our services.
- Import the other application context configuration classes by annotating the class with the @Import annotation.
- Annotate the class with the @PropertySource annotation, and configure it to look for a properties file called application.properties from the classpath. This ensures that the configuration properties can be accessed in the imported application context configuration classes.
- Configure the MessageSource bean.
- Configure the PropertyPlaceHolderConfigurer bean.
The source code the ExampleApplicationContext class looks as follows:
import org.springframework.context.MessageSource; import org.springframework.context.annotation.*; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.context.support.ResourceBundleMessageSource; @Configuration @ComponentScan(basePackages = { "net.petrikainulainen.spring.social.signinmvc.user.service" }) @Import({WebAppContext.class, PersistenceContext.class, SecurityContext.class, SocialContext.class}) @PropertySource("classpath:application.properties") public class ExampleApplicationContext { @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("i18n/messages"); messageSource.setUseCodeAsDefaultMessage(true); return messageSource; } @Bean public PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } }
We have now configured the application context of our example application. However, we still have to configure our web application. Let’s see how we can do this by using Java configuration.
Configuring the Web Application
Our last step is to configure our example application. We can do this without web.xml as long as our application is deployed to a servlet 3.0 compliant container.
We can configure the web application by following these steps:
- Create a class which implements the WebApplicationInitializer interface.
- Configure our application by overriding the onStartup() method of the WebApplicationInitializer interface. We can implement this method by following these steps:
- Create the root context of the application and register the ExampleApplicationContext class to the created root context.
- Configure the dispatcher servlet.
C) Configure character encoding filter.
D) Configure the Spring Security filter chain.
E) Configure Sitemesh.
F) Add the context loader listener to the servlet context.
The source code of the ExampleApplicationConfig class looks as follows:
import org.sitemesh.config.ConfigurableSiteMeshFilter; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; import org.springframework.web.filter.CharacterEncodingFilter; import org.springframework.web.filter.DelegatingFilterProxy; import org.springframework.web.servlet.DispatcherServlet; import javax.servlet.*; import java.util.EnumSet; public class ExampleApplicationConfig implements WebApplicationInitializer { @Override public void onStartup(ServletContext servletContext) throws ServletException { AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext(); rootContext.register(ExampleApplicationContext.class); ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext)); dispatcher.setLoadOnStartup(1); dispatcher.addMapping("/"); EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD); CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter(); characterEncodingFilter.setEncoding("UTF-8"); characterEncodingFilter.setForceEncoding(true); FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncoding", characterEncodingFilter); characterEncoding.addMappingForUrlPatterns(dispatcherTypes, true, "/*"); FilterRegistration.Dynamic security = servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy()); security.addMappingForUrlPatterns(dispatcherTypes, true, "/*"); FilterRegistration.Dynamic sitemesh = servletContext.addFilter("sitemesh", new ConfigurableSiteMeshFilter()); sitemesh.addMappingForUrlPatterns(dispatcherTypes, true, "*.jsp"); servletContext.addListener(new ContextLoaderListener(rootContext)); } }
What is Next?
We have now successfully configured our example application by using Java configuration. This tutorial has taught us two things:
- We learned how we can implement the components required by Spring Security and Spring Social.
- We learned to integrate Spring Security and Spring Social by using Java configuration.
The next part of this tutorial describes how we can add registration and authentication functions to our example application.
P.S. As always, the example application of this blog post is available at Github.
Normally, I really like the quality of your posts, but this one is patently irresponsible. Are you seriously advocating the use of social logins? Are you planning to follow it up with a tutorial on implementing RSA’s BSAFE? Please lead; please stop pandering.
Did you write this comment because you think that social login is not safe?
Hi why is this solution not good for restful api using spring security ?
Thanks for the great article
Well, you can this solution with a restful API but keep in mind that this solution creates a session for each user. If you want to create a stateless API, you have to tweak the configuration to use e.g. token based authentication.
thanks.for.the reply
can.you point me.to how to twik.it.to token base
couldn’t figured it.out yet
thanks again!
I tried the application but it doesn’t work. Some of the plugin is not available in eclipse market and it throws the error.
Eclipse’s poor Maven support has got nothing to do with the example application. It does work when you run it by using Maven (or a working IDE such as Netbeans or IntelliJ Idea).
If you are talking about the JaCoCo plugin, you can just remove it because it is not required to run the example.
Hi Petri,
Thanks for the detailed explanation.
Please I need the below clarification.
In the SimpleSocialUserDetailsService class you have used a UserDetailsService (userDetailsService) , but I couldn’t find any of its implementations within your article.
And also please be kind to explain the data flow in a abstract level. Because i’m confused whether these SocialUserDetailService get data from our DB of from the social media we are connecting to.
Finally and again thanks for this well explained article.
Hi,
the second part of this tutorial explains the social sign in flow with more details (it has a few diagrams which illustrate the registration / sign in flow). If these diagrams are not good enough or you still have problems, feel free to leave comment to that blog post! I will be happy to help you out.
Forgot the link. You can find the second part here: http://www.javacodegeeks.com/2013/10/adding-social-sign-in-to-a-spring-mvc-web-application-registration-and-login.html
Why it requires the web application not be based on REST?
And how can I integrate Spring Social with a REST web application?
Thanks.
oh… I saw your answer at the top of the posts.
Can you direct us to some example?
I wish that I would know an example that uses Spring Social with a REST API. Unfortunately I haven’t found any examples that would do it.
If you happen to find a good example, let me know.
nice
I just moved to full JavaConfig, but my custom signController and connectController beans are not getting picked up by MVC configuration for some reason. Any suggestions? http://stackoverflow.com/questions/38873882/bean-controller-not-getting-picked-up-by-enablemvc