Harmonic Development

RSS
Dec 1

Adding Spring Social to a Spring MVC and Spring Security Web App, Part 1

This series of posts will explain step by step how to add Spring Social to an existing Web application that uses Spring MVC and Spring Security. You will add support for new users to sign in to your application using their Facebook/Twitter accounts. You will add support for current users to associate their Facebook/Twitter accounts with their accounts in your application, so that they too can log in to your application using their Facebook/Twitter accounts. Your application can then also act on a user’s behalf on Facebook/Twitter.

Much of this information is drawn from the Spring Social Core reference documentation. I found myself jumping back and forth between my code and that documentation and feeling like, while the documentation did present most of the necessary information, it did not necessarily present that information in the order in which it became relevant to me during the process of integrating Spring Social into an app. So, I thought I would try to publish a step by step guide to supplement the documentation.

I am going to include information for incorporating both Facebook and Twitter support. If you would like to just include support for one or the other, please note that you will have to edit the example configurations and code appropriately.

Add Core Dependencies

First you will need to add the Spring Social jar files to your project. I use Maven to manage the dependencies of my app. If you do not, you will need to go to the Spring Social site, download the core, Twitter, and Facebook archives, decompress them, and copy the jar files into your application’s lib directory.

If you do use Maven, you will need to make the following changes to your pom.xml file:

1. I recommend you first add properties for the version numbers for the various libraries:

  <properties>
    <spring-social.version>1.0.0.RELEASE</spring-social.version>
    <spring-social-twitter.version>1.0.0.RELEASE</spring-social-twitter.version>
    <spring-social-facebook.version>1.0.0.RELEASE</spring-social-facebook.version>
  </properties>

Note that the Facebook and Twitter libraries are on their own release schedules and will not necessarily always have the same version number as the core Spring Social libraries. Hence, they should have their own version properties.

2. Add the following dependencies:

    <!— Spring Social Core —>
    <dependency>
      <groupId>org.springframework.social</groupId>
      <artifactId>spring-social-core</artifactId>
      <version>${spring-social.version}</version>
    </dependency>
    <!— Spring Social Web (contains login/signup controllers) —>
    <dependency>
      <groupId>org.springframework.social</groupId>
      <artifactId>spring-social-web</artifactId>
      <version>${spring-social.version}</version>
    </dependency>
    <!— Spring Social Twitter —>
    <dependency>
      <groupId>org.springframework.social</groupId>
      <artifactId>spring-social-twitter</artifactId>
      <version>${spring-social-twitter.version}</version>
    </dependency>
    <!— Spring Social Facebook —>
    <dependency>
      <groupId>org.springframework.social</groupId>
      <artifactId>spring-social-facebook</artifactId>
      <version>${spring-social-facebook.version}</version>
    </dependency>

Optional Dependency: Spring Security Crypto

If you plan to use Twitter or any other OAuth1 service provider, or you plan to encrypt the OAuth token/secret values of your users in your database (which is a good idea), you’ll want to also include the Spring Security Crypto library. Unfortunately, this library is only available in Spring Security 3.1 which has not had a final release yet. If you are not using Maven, you will need to get the jars from the Spring Security downloads page. If you are using Maven, you will first need to add the following repository:

  <repositories>
    <repository>
        <id>org.springframework.maven.milestone</id>
        <name>Spring Maven Milestone Repository</name>
        <url>http://maven.springframework.org/milestone</url>
    </repository>
  </repositories>

Then with that repository in place you can add the Spring Security Crypto dependency:

    <!— Spring Security Crypto, required if you use: —>
    <!—   any OAuth1 service provider (e.g. Twitter) —>
    <!—   the provided JDBC connection repository classes —>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-crypto</artifactId>
      <version>3.1.0.RC3</version>
    </dependency>

Social User Connection Entity

OK! The first thing you will want to do now that you’ve added your dependencies is figure out how you are going to persist social connection information for your users.

The reference documentation explains that Spring Social Core comes with classes for persisting connection info to a relational database using JDBC. A SQL file is provided in the core jar which you can use to create the necessary table. If you use the SQL and provided classes you do not need to write an entity class to model that data.

I personally use JPA annotations and Hibernate (managed by Spring). So I went ahead and wrote an entity class to model the social connection data:

@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = { “userId”, “providerId”, “providerUserId” }),
                            @UniqueConstraint(columnNames = { “userId”, “providerId”, “rank” })})
public class SocialUser {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private int id;

  /**
   * A local identifier for the user, in our case the username.
   */
  private String userId;

  @Column(nullable = false)
  private String providerId;

  private String providerUserId;

  @Column(nullable = false)
  private int rank;

  private String displayName;

  private String profileUrl;

  private String imageUrl;

  @Column(nullable = false)
  private String accessToken;

  private String secret;

  private String refreshToken;

  private Long expireTime;


  private Date createDate = new Date();

// getters and setters omitted for brevity

}

This class is based on the SQL definition provided in the Spring Social reference documentation, with the addition of a numerical auto-generated primary key (mostly out of habit I guess), and a create date timestamp for my own information.

Here is a brief overview of the fields:

  • userId - the reference documentation is not very specific about what value should be used in the “userId” column/field, but based on feedback from the Spring Social forum and from trial and error I suggest using your user’s unique username in your application
  • providerId - this is the string provider id value, e.g. “facebook”, “twitter”, etc.
  • providerUserId - this is the user’s unique id in the provider’s system
  • rank - Spring Social actually allows for 1-n accounts per provider per user (e.g. multiple Facebook accounts associated with one user in your application), and this value determines the order of importance of those accounts; generally though there will just be 1 account per provider per user and this value will generally be 1
  • displayName, profileUrl, imageUrl - some profile data fields that may or may not be sent to your application by the provider
  • accessToken, secret, refreshToken, expireTime - these are the OAuth credentials and related information, and you will likely want to encrypt the accessToken and secret values in your database

Implement Connection Persistence Interfaces

As mentioned in the reference documentation there are two interfaces involved in connection persistence:

  • ConnectionRepository - handles connection persistence methods for one specific user; the implementation bean will be request scoped, created for logged in users of your application
  • UsersConnectionRepository - handles connection persistence methods across all users; this will be a normal singleton bean in your application context

Spring Social Core comes with implementations that work with a relational database using JDBC. If you use those implementations, you do not need to write your own implementations of these interfaces. More information will be presented later about configuring your app to use those classes.

If you use JPA for your persistence code in your application, you may want to look at this user contributed Spring Social JPA project. I attempted to use that code but felt like it didn’t match up well with how I had structured the code in my app. However, it was a good starting point for my own implementations of ConnectionRepository and UsersConnectionRepository. There are a lot of queries involved and you also have to do some transformation of method arguments and query results to get things working, and some of that work has been done for you in the JPA project.

I personally use Hibernate and Spring for my persistence code, so I wrote a SocialUserDAO interface and implementation. Here are the important finder methods from the DAO:

  List<SocialUser> findByUserId(String userId);

  List<SocialUser> findByUserIdAndProviderId(String userId, String providerId);

  List<SocialUser> findByUserIdAndProviderUserIds(String userId, MultiValueMap<String, String> providerUserIds);

  SocialUser get(String userId, String providerId, String providerUserId);

  List<SocialUser> findPrimaryByUserIdAndProviderId(String userId, String providerId);

  Integer selectMaxRankByUserIdAndProviderId(String userId, String providerId);

  List<String> findUserIdsByProviderIdAndProviderUserId(String providerId, String providerUserId);

  List<String> findUserIdsByProviderIdAndProviderUserIds(String providerId, Set<String> providerUserIds);

Implementing most of these is fairly straightforward, with the exception of “findByUserIdAndProviderUserIds()”. Make sure that you get the boolean logic correct. You should use something like this pseudo query: “where userId = (userId) AND ((providerId = providerId1 AND providerUserId = providerUserId1) OR (providerId = providerId2 AND providerUserId = providerUserId2) … etc.)”.

Next I wrote an implementation of ConnectionRepository. This is the request scoped bean for logged in users, for working with a single user’s Spring Social connection information. This bean will be instantiated by the UsersConnectionRepository and will have its dependencies passed to its constructor.

public class SocialUserConnectionRepositoryImpl implements ConnectionRepository {

  private String userId;
  private SocialUserDAO socialUserDAO;
  private ConnectionFactoryLocator connectionFactoryLocator;
  private TextEncryptor textEncryptor;

  public SocialUserConnectionRepositoryImpl(String userId, SocialUserDAO socialUserDAO,
                                            ConnectionFactoryLocator connectionFactoryLocator,
                                            TextEncryptor textEncryptor) {
    this.userId = userId;
    this.socialUserDAO = socialUserDAO;
    this.connectionFactoryLocator = connectionFactoryLocator;
    this.textEncryptor = textEncryptor;
  }

Here’s a quick rundown of the fields/dependencies:

  • userId - the userId of the logged in user (as previously stated, this should be the unique username of the user in your application)
  • socialUserDAO - this is my DAO class, this may be different for you depending on how you handle your persistence
  • connectionFactoryLocator - this is the core interface of Spring Social, it provides access to connection factories for all the providers (e.g. Facebook, Twitter, etc.) that you have configured for your app
  • textEncryptor - a Spring Security Crypto class for encrypting/decrypting OAuth credentials stored in your database

Given these (or equivalent) dependencies, implementing the ConnectionRepository interface is relatively straightforward. You will likely need to write a few methods in your class to perform the following tasks:

  • creating a ConnectionData instance based on an instance of your local social user entity class (this is where you’d decrypt OAuth credentials)
  • creating a local social user entity instance based on a ConnectionData instance (this is where you’d encrypt OAuth credentials)
  • using the ConnectionFactoryLocator to create a Connection<?> instance from a ConnectionData instance

Next up comes implementing UsersConnectionRepository. Fortunately this interface only has three methods and is much simpler to implement than ConnectionRepository.

Your implementation should have the following or equivalent fields/dependencies:

  • a DAO or other class for your social user entity data access methods
  • an encryption password, provided from a configuration file, used to initialize the TextEncryptor
  • a ConnectionFactoryLocator and a TextEncryptor (these will just be used as constructor arguments when instantiating your ConnectionRepository implementation for users)
  • you may also want a configurable boolean that turns encryption/decryption on and off so that you can disable encryption of OAuth tokens in development

Here is an example of how you can initialize your TextEncryptor instance in code (instead of XML) if you so choose:

  @PostConstruct
  public void initializeTextEncryptor() {
    textEncryptor = Encryptors.text(encryptionPassword, KeyGenerators.string().generateKey());
  }

Next Time: Spring Social, MVC, and Security Configuration

That’s all for Part 2. Next time we will pick up with configuration of your ConnectionRepository and UsersConnectionRepository implementations, as well as the core Spring Social class, ConnectionFactoryLocator, and ConnectionFactory implementations for Facebook and Twitter. From there, we’ll implement two more interfaces, for Spring Social Web, and begin modifying the MVC portion of our app.

On to Part 2!