Spring in Practice

Willie Wheeler's Spring blog

Using Spring Social GitHub to Access Secured GitHub Data

| Comments

In this post, I shared some screenshots of an open source, Spring-based CMDB I’m building called Zkybase. In the current post I want to show some of the code and configuration that’s required to make OAuth2-authorized Spring/GitHub integration work.

Zkybase screenshot

Spring application configuration

This goes in the app context. I lifted it more or less as-is from the Spring Social reference documentation, except that I’m using GitHub as the provider instead of Facebook, Twitter or LinkedIn.

<bean id="connectionFactoryLocator" class="org.springframework.social.connect.support.ConnectionFactoryRegistry">
    <property name="connectionFactories">
        <list>
            <bean class="org.springframework.social.github.connect.GitHubConnectionFactory">
                <constructor-arg value="${gitHub.clientId}" />
                <constructor-arg value="${gitHub.clientSecret}" />
            </bean>
        </list>
    </property>
</bean>

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

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

<!-- This requires authentication via Spring Security -->
<bean id="connectionRepository"
    factory-bean="usersConnectionRepository"
    factory-method="createConnectionRepository" 
    scope="request">
    
    <constructor-arg value="#{request.userPrincipal.name}" />
    <aop:scoped-proxy proxy-target-class="false" />
</bean>

Spring web configuration

<bean class="org.springframework.social.connect.web.ConnectController" />

Service code for the user account page

Here’s how I’m looking up the GitHub user profile information:

package org.skydingo.zkybase.service.impl;

import javax.inject.Inject;

import org.skydingo.zkybase.model.UserAccount;
import org.skydingo.zkybase.repository.UserAccountRepository;
import org.skydingo.zkybase.service.UserAccountService;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.github.api.GitHub;
import org.springframework.social.github.api.GitHubUserProfile;
import org.springframework.social.github.api.impl.GitHubTemplate;
import org.springframework.stereotype.Service;

@Service
public class UserAccountServiceImpl extends AbstractCIService implements UserAccountService {
    @Inject private ConnectionRepository connectionRepo;
    
    public GitHubUserProfile getCurrentUserProfile() {
        if (gitHub().isAuthorized()) {
            return gitHub().userOperations().getUserProfile();
        } else {
            return null;
        }
    }
    
    private GitHub gitHub() {
        Connection conn = connectionRepo.findPrimaryConnection(GitHub.class);
        return (conn != null ? conn.getApi() : new GitHubTemplate());
    }

    ... other methods ...
}

In the gitHub() method you can see that I return an existing GitHub object if the connection exists; otherwise I just create a new GitHub (in the form of the template, which implements GitHub) so that the app can at least perform non-sensitive read operations.

Service to get the GitHub service hooks

This one’s pretty similar to the above.

package org.skydingo.zkybase.service.impl;

import java.util.List;
import javax.inject.Inject;
import org.skydingo.zkybase.model.Application;
import org.skydingo.zkybase.service.ApplicationService;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.github.api.GitHub;
import org.springframework.social.github.api.GitHubHook;
import org.springframework.social.github.api.impl.GitHubTemplate;
import org.springframework.stereotype.Service;

@Service
public class ApplicationServiceImpl extends AbstractCIService implements ApplicationService {
    @Inject private ConnectionRepository connectionRepo;
    
    public List findHooks(String user, String repo) {
        return gitHub().repoOperations().getHooks(user, repo);
    }

    private GitHub gitHub() {
        Connection conn = connectionRepo.findPrimaryConnection(GitHub.class);
        return (conn != null ? conn.getApi() : new GitHubTemplate());
    }

    ... various other methods ...
}

I should probably factor out that common gitHub() method. :–)

User account JSP

Here’s the JSP code for rendering the connect/disconnect buttons. The CSS classes come from Bootstrap 2.0, in case you were wondering.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
<%@ taglib prefix="social" uri="http://www.springframework.org/spring-social/social/tags" %>

<c:url var="connectUrl" value="/connect" />
<c:url var="githubUrl" value="/connect/github" />

<sec:authentication var="user" property="principal" />

<h1>Account details</h1>

<section class="first">
    <div class="well">
        <table class="grid">
            <tr>
                <td>Username:</td>
                <td><c:out value="${user.username}" /></td>
            </tr>
        </table>
    </div>
</section>

<section>
    <h2>GitHub</h2>

    <social:connected provider="github">
        <p>Your Zkybase and GitHub accounts are connected.</p>
        <div class="well">
            <table class="grid">
                <tr>
                    <td>Blog:</td>
                    <td><c:out value="${gitHubUserProfile.blog}" default="None" /></td>
                </tr>
                <tr>
                    <td>Location:</td>
                    <td><c:out value="${gitHubUserProfile.location}" default="None" /></td>
                </tr>
            </table>
        </div>
        <form method="post" action="${githubUrl}">
            <input type="hidden" name="_method" value="delete" />
            <input class="btn btn-danger" type="submit" value="Disconnect from GitHub" />
        </form>
    </social:connected>

    <social:notConnected provider="github">
        <p>Your Zkybase and GitHub accounts are not yet connected. Connect them for additional Zkybase features.</p>
        <form method="post" action="${githubUrl}">
            <input type="hidden" name="scope" value="user, repo, gist" />
            <input class="btn btn-primary" type="submit" value="Connect to GitHub" />
        </form>
    </social:notConnected>
</section>

Note the use of the Spring Social <social:connected> and <social:notConnected> tags to show different content based on whether the user is or isn’t connected to the provider in question.

Notice also that the connect button sends a hidden scope parameter to the GitHub URL. This allows us to specify the kinds of operation we want the user to authorize.

Anyway, there you have it. This post was a bit quick, so let me know if I left anything important out and I’m happy to add it. Have fun.

Comments