Trifork Blog

Using Spring social to connect your online profiles

March 15th, 2011 by
| Reply

Some time a go I wrote an item on my personal blog about a sample that I created with the spring social project. I explained how to connect to linkedin. In this blog post I want to go one step further. I want to create an application that a user need to log in to. With that application I want to gather events from different online communities that are provided by spring-social out of the box. I will also provide some notes on upgrading my sample from Spring social M1 to Spring social M2.

Upgrading the sample was some work. But in the end it was all about making things easier. So it was a good upgrade. In short I had to change some dependencies. Moved to spring core 3.1 M1, had to introduce spring security of which I also took the 3.1 RC1 version. Next to these upgrades I had to add some dependencies. I had to add the spring-social modules for linkedin, twitter and web.

Source code

The sources are hosted on github: https://github.com/jettro/SpringOAuthLinkedinIntegrationSample

What is removed?

In the first version of the sample I had to create a ConnectController with methods that handled the OAuth calls. With the M2 release this has all become a lot easier. Now we have a ConnectController provided by the spring-social-web module that does all this OAuth handling for you. The ConnectController uses a ServiceProvider based on the provider id. The provider id is taken from the url of the request. As an example: /connect/linkedin connects to linked in. You did not see that one coming did you? Before we move on to the service providers I want to mention one last thing. The ConnectController wants a application.url paramter to provide as a callback parameter.

Connecting to service providers

Service providers like the ones linkedin and twitter provide an api to connect to and read data from. Sometimes you can also provide data. The key component for this kind of interactions is the ServiceProvider class. All concrete implementations of this ServiceProvider extend the OAuth1 or the OAuth2 abstract super class. I do not want to go into to much details. Please refer to the reference manual for more details.

Usually a connection requires an application secret key and public key. That enables the application to interact with the service provider. When your visitors want to use the application to connect to their profile they have to provide you access. After the authentication process you obtain a key that you can keep for the next session. Storing these keys is also provided by the ConnectionRepository, more on this later on.

You can use the java configuration of spring, or the xml configuration. For now I prefer the xml. The following code block shows the configuration of one of these service providers.

<bean class="org.springframework.social.twitter.connect.TwitterServiceProvider">
    <constructor-arg value="${twitter.consumerKey}" />
    <constructor-arg value="${twitter.consumerSecret}" />
    <constructor-arg ref="connectionRepository" />
</bean>

As you can see from the sourcecode, we provide the consumer key and the consumer secret. Next to these two parameters we also provide the ConnectionRepository. These parameters are usually obtained through a developer page on the site the service provider is connecting to. Examples are http://dev.twitter.com/ and http://developer.linkedin.com/community/apis.

If you want to have a better understanding of the OAuth connections that are going on, have a look at my other blogpost.

Connection Repository

In my previous version of the sample, you had to connect every time again to the service provider. The actual keys that you need to connect were not store. With the M2 release of spring social this has become so easy, it would be silly not to use it. You need a datasource, can use a password encoder and you need the ConnectionRepository. The following code block shows the xml configuration.

<bean id="connectionRepository" class="org.springframework.social.connect.jdbc.JdbcConnectionRepository">
    <constructor-arg ref="dataSource"/>
    <constructor-arg ref="textEncryptor"/>
</bean>

<bean id="textEncryptor" class="org.springframework.security.crypto.encrypt.Encryptors" factory-method="noOpText"/>

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

Before you can actually use the jdbc connection repository you need a database with a schema with a Connections table. The sql query is provided in the source code of spring-social. I wanted to use mysql, therefore I have created the following create statement.

Database schema

CREATE TABLE `Connection` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `accountId` varchar(255) NOT NULL DEFAULT '',
  `providerId` varchar(255) NOT NULL DEFAULT '',
  `accessToken` varchar(255) NOT NULL DEFAULT '',
  `secret` varchar(255) DEFAULT NULL,
  `refreshToken` varchar(255) DEFAULT NULL,
  `providerAccountId` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;;

Handling user interaction

The service providers need a few jsps for interacting with the visitors. These jsps contain a form to submit a connect request and another jsp that the user gets to see if he has already connected. Check the source code if you want to see these jsps.

Finally I want to have a short look at the usage of the api. I'll briefly go through the api of linkedin and the one of twitter.

Linkedin API

I have created the LinkedinController that handles all api calls to linked in. I have two sample, one to obtain all contacts and one to obtain your profile. I'll show the method for obtaining all your contacts.

    @RequestMapping(value = "/connect/linkedin/connections", method = RequestMethod.GET)
    public String connections(WebRequest request) {
        request.setAttribute("connections", linkedinApi().getConnections(), WebRequest.SCOPE_REQUEST);

        return "linkedin/connections";
    }

    private LinkedInApi linkedinApi() {
        User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return linkedInServiceProvider.getConnections(principal.getUsername()).get(0).getServiceApi();
    }

One important thing to notice is in the last line. You can see that the user can have multiple connections. For the result we only take the first one. That is it, really.

Twitter API

If you have seen the linkedin sample, the twitter sample should not be to hard either. The following code block shows you how to obtain information about your twitter profile.

    @RequestMapping(value = "/connect/twitter/profile", method = RequestMethod.GET)
    public String obtainProfile(WebRequest request) {

        request.setAttribute("profile", twitterApi().getUserProfile(), WebRequest.SCOPE_REQUEST);
        return "twitter/profile";
    }

    private TwitterApi twitterApi() {
        User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return twitterServiceProvider.getConnections(principal.getUsername()).get(0).getServiceApi();
    }

Just like in the linkedin sample, we obtain the name of the logged in user using the spring security SecurityContextHolder.

Screen shot 2011 03 15 at 21 17 06

Concluding

I am still amazed to see how easy it is to connect to social media like linkedin and twitter using the spring-social project. After reading this blog I hope you agree that the library is very effective. I will do some more stuff with the spring-social project, if I find other interesting things I'll let you know.

References

9 Responses

  1. March 16, 2011 at 13:19 by Hans Westerbeek

    And with Google also introducing oAuth2 for their API's this is just getting better and better

  2. March 16, 2011 at 13:20 by Hans Westerbeek

    I should probably mention the link: http://code.google.com/apis/accounts/docs/OAuth2.html

  3. June 2, 2011 at 17:22 by anil

    Hi Jettro Coenradie,

    You posted very good tutorial, I checked with your code.
    Its working fine.

    I found one small issue, you are restricting the app for only two users right ?

    I want generalize like every one can access without intial application authentication.

    Only LinkedIn authentication is required.

    what are the changes in the app is required..?

  4. June 5, 2011 at 14:25 by Jettro Coenradie

    I am not aware of this limitation, I will have a look at it.

  5. June 8, 2011 at 18:20 by Antony

    Hi

    I jsut tried this using embedded hsqldb and get this error when I click on the linkedin link after logging in.

    PreparedStatementCallback; bad SQL grammar [select exists(select 1 from Connection where accountId = ? and providerId = ?)]; nested exception is java.sql.SQLException: Unexpected token , requires FROM in statement [select exists(select 1 from Connection where accountId = ? and providerId = ?)]

    Any idea on why I get this error?

  6. June 9, 2011 at 15:44 by Jettro Coenradie

    Hi Antony,
    you do need to create something to create the database. This is not done automatically. I used mysql, the sql to create the table is included in the source code and the blog post.

    Hope that helps

  7. October 20, 2011 at 21:08 by Binod Suman

    Hi,

    Really tutorial is very good and very helpful who want to start coding on social networking using java. I like your way how you have explained these things in very easy steps.

    Thanks for sharing these stuff.

    Thanks,

    Binod Suman
    Bangalore, India

  8. April 1, 2012 at 03:03 by jije

    ERROR - 2-thread-1 - ContextLoader - Context initialization failed
    org.springframework.beans.factory.BeanInitializationException: Could not load properties; nested exception is java.io.FileNotFoundException: class path resource [config.properties] cannot be opened because it does not exist
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.postProcessBeanFactory(PropertySourcesPlaceholderConfigurer.java:130)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:668)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:643)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:437)
    at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:294)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:215)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:47)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4779)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5273)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1566)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1556)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
    at java.util.concurrent.FutureTask.run(FutureTask.java:166)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:722)
    Caused by: java.io.FileNotFoundException: class path resource [config.properties] cannot be opened because it does not exist
    at org.springframework.core.io.ClassPathResource.getInputStream(ClassPathResource.java:158)
    at org.springframework.core.io.support.PropertiesLoaderSupport.loadProperties(PropertiesLoaderSupport.java:181)
    at org.springframework.core.io.support.PropertiesLoaderSupport.mergeProperties(PropertiesLoaderSupport.java:161)
    at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.postProcessBeanFactory(PropertySourcesPlaceholderConfigurer.java:121)
    ... 16 more

  9. April 2, 2012 at 10:12 by Jettro Coenradie

    If you look close at the error message, it is missing the file config.properties. The source code contains a template for this file, but you need to create your own.

    regards Jettro

Leave a Reply