Enterprise Java

Getting Started with Spring Social – Part 2

A few weeks ago I wrote a post demonstrating what I thought was the simplest application you could write using Spring Social. This application read and displayed a Twitter user’s public data and was written as an introduction to Spring Social and the social coding arena. However, getting your application to display your user’s public data is only half the story and most of the time you’ll need to display your user’s private data.

In this blog, I’m going to cover the scenario where you have a requirement to display a user’s Facebook or other Software as a Service (SaaS) provider data on one or two pages of your application. The idea here is to try to demonstrate the smallest and simplest thing you can to to add Spring Social to an application that requires your user to log in to Facebook or other SaaS provider.

Creating the App

To create the application, the first step is to create a basic Spring MVC Project using the template section of the SpringSource Toolkit Dashboard. This provides a webapp that’ll get you started.

The next step is to set up the pom.xml by adding the following dependencies:

<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-crypto</artifactId>
 <version>${org.springframework.security.crypto-version}</version>
</dependency>

<!-- Spring Social -->
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-core</artifactId>
 <version>${spring-social.version}</version>
</dependency>  
<dependency>
 <groupId>org.springframework.social</groupId>
 <artifactId>spring-social-web</artifactId>
 <version>${spring-social.version}</version>
</dependency>
  
<!-- Facebook API -->
<dependency>
  <groupId>org.springframework.social</groupId>
  <artifactId>spring-social-facebook</artifactId>
  <version>${org.springframework.social-facebook-version}</version>
</dependency>

<!-- JdbcUserConfiguration -->
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-jdbc</artifactId>
 <version>${org.springframework-version}</version>
</dependency> 
<dependency>
 <groupId>com.h2database</groupId>
 <artifactId>h2</artifactId>
 <version>1.3.159</version>
</dependency>

<!-- CGLIB, only required and used for @Configuration usage: could be removed in future release of Spring -->
<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib-nodep</artifactId>
 <version>2.2</version>
</dependency>

…obviously you’ll also need to add the following to the %lt;properties/> section of the file:

<spring-social.version>1.0.2.RELEASE</spring-social.version>
<org.springframework.social-facebook-version>1.0.1.RELEASE</org.springframework.social-facebook-version>
<org.springframework.security.crypto-version>3.1.0.RELEASE</org.springframework.security.crypto-version>

You’ll notice that I’ve added a specific pom entry for spring-security-crypto: this is because I’m using Spring 3.0.6. In Spring 3.1.x, this has become part of the core libraries.

The only other point to note is that there is also a dependency on spring-jdbc and h2. This is because Spring’s UserConnectionRepository default implementation: JdbcUsersConnectionRepository uses them and hence they’re required even though this app doesn’t persist anything to a database (so far as I can tell).

The Classes

The social coding functionality consists of four classes (and one of those I’ve pinched from Keith Donald’s Spring Social Quick Start Sample code):

  • FacebookPostsController
  • SocialContext
  • FacebookConfig
  • UserCookieGenerator

FacebookPostsController is the business end of the application responsible for getting hold of the user’s Facebook data and pushing it into the model ready for display.

@Controller

public class FacebookPostsController {



  private static final Logger logger = LoggerFactory.getLogger(FacebookPostsController.class);



  private final SocialContext socialContext;



  @Autowired

  public FacebookPostsController(SocialContext socialContext) {

    this.socialContext = socialContext;

  }



  @RequestMapping(value = 'posts', method = RequestMethod.GET)

  public String showPostsForUser(HttpServletRequest request, HttpServletResponse response, Model model) throws Exception {



    String nextView;



    if (socialContext.isSignedIn(request, response)) {



      List<Post> posts = retrievePosts();

      model.addAttribute('posts', posts);

      nextView = 'show-posts';

    } else {

      nextView = 'signin';

    }



    return nextView;

  }



  private List<Post> retrievePosts() {



    Facebook facebook = socialContext.getFacebook();

    FeedOperations feedOps = facebook.feedOperations();



    List<Post> posts = feedOps.getHomeFeed();

    logger.info('Retrieved ' + posts.size() + ' posts from the Facebook authenticated user');

    return posts;

  }

}

As you can see, from a high-level viewpoint the logic of what we’re trying to achieve is pretty simple:

IF user is signed in THEN 
     read Facebook data, 
     display Facebook data 
ELSE 
    ask user to sign in 
    when user has signed in, go back to the beginning
END IF

The FacebookPostsController delegates the task of handling the sign in logic to the SocialContext class. You can probably guess that I got the idea for this class from Spring’s really useful ApplicationContext. The idea here is that there is one class that’s responsible for gluing your application to Spring Social.

public class SocialContext implements ConnectionSignUp, SignInAdapter {



  /**

   * Use a random number generator to generate IDs to avoid cookie clashes

   * between server restarts

   */

  private static Random rand;



  /**

   * Manage cookies - Use cookies to remember state between calls to the

   * server(s)

   */

  private final UserCookieGenerator userCookieGenerator;



  /** Store the user id between calls to the server */

  private static final ThreadLocal<String> currentUser = new ThreadLocal<String>();



  private final UsersConnectionRepository connectionRepository;



  private final Facebook facebook;



  public SocialContext(UsersConnectionRepository connectionRepository, UserCookieGenerator userCookieGenerator,

      Facebook facebook) {

    this.connectionRepository = connectionRepository;

    this.userCookieGenerator = userCookieGenerator;

    this.facebook = facebook;



    rand = new Random(Calendar.getInstance().getTimeInMillis());

  }



  @Override

  public String signIn(String userId, Connection<?> connection, NativeWebRequest request) {

    userCookieGenerator.addCookie(userId, request.getNativeResponse(HttpServletResponse.class));

    return null;

  }



  @Override

  public String execute(Connection<?> connection) {

    return Long.toString(rand.nextLong());

  }



  public boolean isSignedIn(HttpServletRequest request, HttpServletResponse response) {



    boolean retVal = false;

    String userId = userCookieGenerator.readCookieValue(request);

    if (isValidId(userId)) {



      if (isConnectedFacebookUser(userId)) {

        retVal = true;

      } else {

        userCookieGenerator.removeCookie(response);

      }

    }



    currentUser.set(userId);

    return retVal;

  }



  private boolean isValidId(String id) {

    return isNotNull(id) && (id.length() > 0);

  }



  private boolean isNotNull(Object obj) {

    return obj != null;

  }



  private boolean isConnectedFacebookUser(String userId) {



    ConnectionRepository connectionRepo = connectionRepository.createConnectionRepository(userId);

    Connection<Facebook> facebookConnection = connectionRepo.findPrimaryConnection(Facebook.class);

    return facebookConnection != null;

  }



  public String getUserId() {



    return currentUser.get();

  }



  public Facebook getFacebook() {

    return facebook;

  }



}

SocialContext implements Spring Social’s ConnectionSignUp and SignInAdapter interfaces. It contains three methods isSignedIn(), signIn(), execute(). isSignedIn is called by the FacebookPostsController class to implement the logic above, whilst signIn() and execute() are called by Spring Social.

From my previous blogs you’ll remember that OAuth requires lots of trips between the browser, your app and the SaaS provider. In making these trips the application needs to save the state of several OAuth arguments such as: client_id, redirect_uri and others. Spring Social hides all this complexity from your application by mapping the state of the OAuth conversation to one variable that your webapp controls. This is the userId; however, don’t think of think of this as a user name because it’s never seen by the user, it’s just a unique identifier that links a number of HTTP requests to an SaaS provider connection (such as Facebook) in the Spring Social core.

Because of its simplicity, I’ve followed Keith Donald’s idea of using cookies to pass the user id between the browser and the server in order to maintain state. I’ve also borrowed his UserCookieGenerator class from the Spring Social Quick Start to help me along.

The isSignedIn(...) method uses UserCookieGenerator to figure out if the HttpServletRequest object contains a cookie that contains a valid user id. If it does then it also figures out if Spring Social’s UsersConnectionRepository contains a ConnectionRepository linked to the same user id. If both of these tests return true then the application will request and display the user’s Facebook data. If one of the two tests returns false, then the user will be asked to sign in.

SocialContext has been written specifically for this sample and contains enough functionality to demonstrate what I’m talking about in this blog. This means that it’s currently a little rough and ready, though it could be improved to cover connections to any / many providers and then reused in different applications.

The final class to mention is FacebookConfig, which is loosely based upon the Spring Social sample code. There are two main differences between this code and the sample code with the first of these being that the FacebookConfig class implements the InitializingBean interface. This is so that the usersConnectionRepositiory variable can be injected into the socialContext and in turn the socialContext can be injected into the usersConnectionRepositiory as its ConnectionSignUp implementation. The second difference is that I’m implementing a providerSignInController(...) method to provide a correctly configured ProviderSignInController object to be used by Spring Social to sign in to Facebook. The only change to the default I’ve made here is to set the ProviderSignInController’s postSignInUrl property to “ /posts”. This is the url of the page that will contain the users Facebook data and will be called once the user sign in is complete.

@Configuration

public class FacebookConfig implements InitializingBean {



  private static final Logger logger = LoggerFactory.getLogger(FacebookConfig.class);



  private static final String appId = '439291719425239';

  private static final String appSecret = '65646c3846ab46f0b44d73bb26087f06';



  private SocialContext socialContext;



  private UsersConnectionRepository usersConnectionRepositiory;



  @Inject

  private DataSource dataSource;



  /**

   * Point to note: the name of the bean is either the name of the method

   * 'socialContext' or can be set by an attribute

   * 

   * @Bean(name='myBean')

   */

  @Bean

  public SocialContext socialContext() {



    return socialContext;

  }



  @Bean

  public ConnectionFactoryLocator connectionFactoryLocator() {



    logger.info('getting connectionFactoryLocator');

    ConnectionFactoryRegistry registry = new ConnectionFactoryRegistry();

    registry.addConnectionFactory(new FacebookConnectionFactory(appId, appSecret));

    return registry;

  }



  /**

   * Singleton data access object providing access to connections across all

   * users.

   */

  @Bean

  public UsersConnectionRepository usersConnectionRepository() {



    return usersConnectionRepositiory;

  }



  /**

   * Request-scoped data access object providing access to the current user's

   * connections.

   */

  @Bean

  @Scope(value = 'request', proxyMode = ScopedProxyMode.INTERFACES)

  public ConnectionRepository connectionRepository() {

    String userId = socialContext.getUserId();

    logger.info('Createung ConnectionRepository for user: ' + userId);

    return usersConnectionRepository().createConnectionRepository(userId);

  }



  /**

   * A proxy to a request-scoped object representing the current user's

   * primary Facebook account.

   * 

   * @throws NotConnectedException

   *             if the user is not connected to facebook.

   */

  @Bean

  @Scope(value = 'request', proxyMode = ScopedProxyMode.INTERFACES)

  public Facebook facebook() {

    return connectionRepository().getPrimaryConnection(Facebook.class).getApi();

  }



  /**

   * Create the ProviderSignInController that handles the OAuth2 stuff and

   * tell it to redirect back to /posts once sign in has completed

   */

  @Bean

  public ProviderSignInController providerSignInController() {

    ProviderSignInController providerSigninController = new ProviderSignInController(connectionFactoryLocator(),

        usersConnectionRepository(), socialContext);

    providerSigninController.setPostSignInUrl('/posts');

    return providerSigninController;

  }



  @Override

  public void afterPropertiesSet() throws Exception {



    JdbcUsersConnectionRepository usersConnectionRepositiory = new JdbcUsersConnectionRepository(dataSource,

        connectionFactoryLocator(), Encryptors.noOpText());



    socialContext = new SocialContext(usersConnectionRepositiory, new UserCookieGenerator(), facebook());



    usersConnectionRepositiory.setConnectionSignUp(socialContext);

    this.usersConnectionRepositiory = usersConnectionRepositiory;

  }

}

Application Flow

If you run this application 2 you’re first presented with the home screen containing a simple link inviting you to display your posts. The first time you click on this link, you’re re-directed to the /signin page. Pressing the ‘sign in’ button tells the ProviderSignInController to contact Facebook. Once authentication is complete, then the ProviderSignInController directs the app back to the /posts page and this time it displays the Facebook data.

Configuration

For completeness, I thought that I should mention the XML config, although there’s not much of it because I’m using the Spring annotation @Configuration on the FacebookConfig class. I have imported “ data.xml” from the Spring Social so that JdbcUsersConnectionRepository works and added

<context:component-scan base-package='com.captaindebug.social' />

…for autowiring.

Summary

Although this sample app is based upon connecting your app to your user’s Facebook data, it can be easily modified to use any of the Spring Social client modules. If you like a challenge, try implementing Sina-Weibo where everything’s in Chinese – it’s a challenge, but Google Translate comes in really useful.

1 Spring Social and Other OAuth Blogs:

  1. Getting Started with Spring Social
  2. Facebook and Twitter: Behind the Scenes
  3. The OAuth Administration Steps
  4. OAuth 2.0 Webapp Flow Overview

2 The code is available on Github at: https://github.com/roghughe/captaindebug.git

Reference: Getting Started with Spring Social – Part 2 from our JCG partner Roger Hughes at the Captain Debug’s Blog blog.

Subscribe
Notify of
guest

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

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Umut Erturk
Umut Erturk
11 years ago

thanks for ur nice tutorial
btw hope it is not your real secret key :P

Paul
Paul
11 years ago

Good tutorial, but you dont explain anything about the view of this MVC arquitecture. The tutorial looks incomplete for me.

Back to top button