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:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<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:

1
2
3
<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.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
@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:

1
2
3
4
5
6
7
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.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
@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

1
<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.

Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy
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
12 years ago

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

Paul
Paul
12 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