Не так давно я уже писал о новом проекте SpringSource: spring-social. Сегодня я хочу рассказать (вернее показать на примере) как можно данную библиотеку использовать. В качестве примера используется простейшее приложение, которое позволяет залогиниться во все три сервиса и обновлять статус одновременно во всех трех (осторожно — под катом много букв и кода и совсем нет картинок).
Обновление статуса было взято в качестве примера потому что:
* это просто и понятно;
* эта функция не поддерживается spring-social для LinkedIn-а, то есть можно посмотреть как добавлять реализацию APIшых методов сервисов, не реализованных в spring-social по умолчанию.
В принципе на месте обновления статуса может быть любая другая функциональность.
Сами разработчики советуют смотреть в качестве примера использования библиотеки их приложение GreenHouse — но, приложение достаточно большое и вычленить имено «социальную» часть не так легко. Было так же опубликовано пара примеров — но достаточно примитивных — не дающих «полной картины». Потому я и решил написать небольшой пример (самому разобраться и другим показать) который бы с одной стороны представлял с собой некоторое законченный пример от логина в сервис до отправки статуса, а с другой стороны был бы максимально простым. Потому — извините что html голый без каких либо стилей — плюс java-код не везде «красив» — в некоторых местах код можно было бы сделать «правильней» например путем наследования, но это бы «зымылило» бы пример — пришлось бы разбираться еще с дополнительной иерархией классов.
Данный пост является вольным переводом моего англоязычного поста.
Все исходники доступны в svn: http://svn.emforge.net/svnroot/akakunin-experiments/update-status
Исходники так же можно просмотреть на сайте EmForge
В качестве «базы» мы используем простейшее приложение на spring framework и spring-mvc. В данном приложении используется стандартный джентльменский набор:
* Maven для сборки;
* Spring Framework 3.0.5;
* Spring-Security 3.0.5;
* JPA (через Hibernate);
* HSQL в качестве базы.
В принципе все максимально стандартно и просто. Есть единственная Entity — net.emforge.updatestatus.entity.User — которая хранит пользователей (userName, password). Для каждого пользователя так же сохраняются токены, которые используются для работы с каждым из сервисов.
Есть DAO-класс UserDao, и сервис UserServiceImpl.
В приложении целых две странички — главная (index) с формой для логина и регистрации (обрабатывается UserController-ом), и страничка status доступная только залогиненым пользователям — о ней мы поговорим позже.
Все вместе это представляет собой простейшее приложение где пользователь может зарегистрироваться или залогиниться, и на страничке status законнектиться с одним из сервисов и обновить статус.
Что бы данный пример работал, вам будет необходимо сначала зарегистрировать свое приложение в тех сервисах, которые вы собираетесь использовать:
* Регистрация на Facebook-е осуществляется на странице http://developers.facebook.com/setup/ — только просьба использовать в качестве SiteURL localhost:8080/update-status/ — иначе callback работать не будет;
* Регистрация в Twitter: http://dev.twitter.com/apps/new;
* Регистрация в LinkedIn: https://www.linkedin.com/secure/developer
В каждом случае вы получите два ключа — ключ приложения (или id приложения) & security-token — их надо сохранить в src/main/resources/config.properties
Если вы хотите по быстрому посмотреть как это работает и у вас есть java & maven (я надеюсь что это так :) ) — то просто возьмите исходники из svn и выполните (после того как пропишите свои ключи в config.properties)
После чего приложение должно быть доступно по адресу http://localhost:8080/update-status
Создайте нового пользователя — и вперед, а мы посмотрим как же это все реализовано
Для работы с обоими сервисами используется OAuth и процедура соединения аналогична — так что покажу на примере twitter-а.
Для работы с сервисом пишем простейший класс TwitterProvider главный метод в котором — getOAuthService:
apiKey, apiSecret & callbackUrl инжектятся из config.properties (куда мы из сначала прописываем).
Для коннекта добавляем ссылку /connet/twitter и вешаем обработчик (в SocialController):
По сути дела данный обработчик делает редирект на Twitter для прохождения логина в нем. В случае успешного логина Twitter сделает редирект на callbackUrl и передаст туда ключи пользователя. Повесим в SocialController обработчик для callBack-а:
В этом обработчике мы получаем токены и сохраняем их для пользователя для дальнейшего использования.
В Facebook-ом все немного хитрее. Для того что бы законнектиться в Facebook мы размещаем на нашу страничку status.jsp кнопку «Connect to Facebook» используя следующий код:
Тут важно обратить внимание на следующие моменты:
* facebook:init тег реализован в библиотеке spring-social и ему требуется, что бы в спринговом контексте был бин ${facebookProvider} — у нас он реализован в классе FacebookProvider. По сути дела из этого бина тег берет только ключи;
* fb:login-button генерит кнопку «Connect to Facebook»;
* form-action /connect/facebook будет использован в качестве callback-а в случае успешного выполнения логина.
Так же для того что бы этот код заработал необходимо подключить tagLib
и подключить jQuery — в моем случае я просто сделал
Ну и надо написать обработчик callback-а, который придет на /connect/facebook:
В данном примере мы увидим две интересные аннотации: @FacebookAccessToken и @FacebookUserId — это одна из фичек spring-social, но что бы она работала, нам необходимо добавить в проект специальный WebArgResolver. Для этого добавляем в applicationContext.xml:
Обработка callback-а аналогична Tiwtter & Linkedin — сохраняем ключи (в нашем случае facebookUserId & facebookToken) для дальнейшего использования.
Законнектиться-законнектились, теперь можно и статус обновлять. На страничке статус у нас есть textArea где пользователь вводит текст и отправляет сообщение. Обработчик формы выглядит следующим образом:
В данном обработчике мы получаем FacebookTemplate & TwitterTemplate (оба класса из spring-social) используя ключи текущего пользователя и вызываем их метод updateStatus
С LinkedIn-ом получилось немного сложней — тот LinkedInTemplate, которые реализован в spring-social не содержит метода для обновления статуса — так что нам придется написать его самим (используя REST-API вызов api.linkedin.com/v1/people/~/person-activities). Для этого мы пишем класс LinkedInTemplateExt — наследник «оригинального» LinkedInTemplate в котором:
* Инициализируем собственный объект класса RestOperations — при этом используется специальный OAuth1RequestSignerFactory.getRequestSigner для «правильного» кодирования вызовов (еще одна фича spring-social);
* Реализуем сам метод updateStatus:
нам так же потребуется написать класс LinkedInPersonActivity — для передачи статуса:
Все — метод готов — как видно — не смотря на то что LinkedInTemplate изначально поддерживает очень мало функций из API — добавление новых функций не такая уж и сложная задача.
Вот вроде и все — надеюсь этот пример окажется полезным.
Обновление статуса было взято в качестве примера потому что:
* это просто и понятно;
* эта функция не поддерживается spring-social для LinkedIn-а, то есть можно посмотреть как добавлять реализацию APIшых методов сервисов, не реализованных в spring-social по умолчанию.
В принципе на месте обновления статуса может быть любая другая функциональность.
Сами разработчики советуют смотреть в качестве примера использования библиотеки их приложение GreenHouse — но, приложение достаточно большое и вычленить имено «социальную» часть не так легко. Было так же опубликовано пара примеров — но достаточно примитивных — не дающих «полной картины». Потому я и решил написать небольшой пример (самому разобраться и другим показать) который бы с одной стороны представлял с собой некоторое законченный пример от логина в сервис до отправки статуса, а с другой стороны был бы максимально простым. Потому — извините что html голый без каких либо стилей — плюс java-код не везде «красив» — в некоторых местах код можно было бы сделать «правильней» например путем наследования, но это бы «зымылило» бы пример — пришлось бы разбираться еще с дополнительной иерархией классов.
Данный пост является вольным переводом моего англоязычного поста.
Все исходники доступны в svn: http://svn.emforge.net/svnroot/akakunin-experiments/update-status
Исходники так же можно просмотреть на сайте EmForge
Базовое приложение
В качестве «базы» мы используем простейшее приложение на spring framework и spring-mvc. В данном приложении используется стандартный джентльменский набор:
* Maven для сборки;
* Spring Framework 3.0.5;
* Spring-Security 3.0.5;
* JPA (через Hibernate);
* HSQL в качестве базы.
В принципе все максимально стандартно и просто. Есть единственная Entity — net.emforge.updatestatus.entity.User — которая хранит пользователей (userName, password). Для каждого пользователя так же сохраняются токены, которые используются для работы с каждым из сервисов.
Есть DAO-класс UserDao, и сервис UserServiceImpl.
В приложении целых две странички — главная (index) с формой для логина и регистрации (обрабатывается UserController-ом), и страничка status доступная только залогиненым пользователям — о ней мы поговорим позже.
Все вместе это представляет собой простейшее приложение где пользователь может зарегистрироваться или залогиниться, и на страничке status законнектиться с одним из сервисов и обновить статус.
Регистрация приложения
Что бы данный пример работал, вам будет необходимо сначала зарегистрировать свое приложение в тех сервисах, которые вы собираетесь использовать:
* Регистрация на Facebook-е осуществляется на странице http://developers.facebook.com/setup/ — только просьба использовать в качестве SiteURL localhost:8080/update-status/ — иначе callback работать не будет;
* Регистрация в Twitter: http://dev.twitter.com/apps/new;
* Регистрация в LinkedIn: https://www.linkedin.com/secure/developer
В каждом случае вы получите два ключа — ключ приложения (или id приложения) & security-token — их надо сохранить в src/main/resources/config.properties
Запустить по быстрому
Если вы хотите по быстрому посмотреть как это работает и у вас есть java & maven (я надеюсь что это так :) ) — то просто возьмите исходники из svn и выполните (после того как пропишите свои ключи в config.properties)
mvn tomcat:run
После чего приложение должно быть доступно по адресу http://localhost:8080/update-status
Создайте нового пользователя — и вперед, а мы посмотрим как же это все реализовано
Коннект в LinkedIn и Twitter
Для работы с обоими сервисами используется OAuth и процедура соединения аналогична — так что покажу на примере twitter-а.
Для работы с сервисом пишем простейший класс TwitterProvider главный метод в котором — getOAuthService:
public OAuthService getOAuthService() {
OAuthConfig config = new OAuthConfig();
config.setRequestTokenEndpoint("https://api.twitter.com/oauth/request_token");
config.setAccessTokenEndpoint("https://api.twitter.com/oauth/access_token");
config.setAccessTokenVerb(Verb.POST);
config.setRequestTokenVerb(Verb.POST);
config.setApiKey(apiKey);
config.setApiSecret(apiSecret);
config.setCallback(callbackUrl);
return new OAuth10aServiceImpl(
new HMACSha1SignatureService(),
new TimestampServiceImpl(),
new BaseStringExtractorImpl(),
new HeaderExtractorImpl(),
new TokenExtractorImpl(),
new TokenExtractorImpl(),
config);
}
apiKey, apiSecret & callbackUrl инжектятся из config.properties (куда мы из сначала прописываем).
Для коннекта добавляем ссылку /connet/twitter и вешаем обработчик (в SocialController):
@RequestMapping(value = "/connect/twitter", method = RequestMethod.GET)
public String requestConnectionToTwitter(WebRequest request) {
// get request token
Token requestToken = twitterProvider.getOAuthService().getRequestToken();
// store request token in session
request.setAttribute("twitter_request_token", requestToken, WebRequest.SCOPE_SESSION);
return "redirect:" + twitterProvider.getAuthorizeUrl() + "?oauth_token=" + requestToken.getToken();
}
По сути дела данный обработчик делает редирект на Twitter для прохождения логина в нем. В случае успешного логина Twitter сделает редирект на callbackUrl и передаст туда ключи пользователя. Повесим в SocialController обработчик для callBack-а:
/** Callback from twitter on success login
*
* @param verifier
* @param request
* @return
*/
@RequestMapping(value = "/callback/twitter", method = RequestMethod.GET, params = "oauth_token")
public String authorizeTwitterCallback(@RequestParam(value = "oauth_verifier", defaultValue = "verifier") String verifier,
WebRequest request) {
// get request token from session
Token requestToken = (Token)request.getAttribute("twitter_request_token", WebRequest.SCOPE_SESSION);
// get access token
Token accessToken = twitterProvider.getOAuthService().getAccessToken(requestToken, new Verifier(verifier));
String userName = getCurrentUser().getName();
userService.updateTwitterAuthentication(userName, accessToken.getToken(), accessToken.getSecret());
return "redirect:/status";
}
В этом обработчике мы получаем токены и сохраняем их для пользователя для дальнейшего использования.
Коннект в Facebook
В Facebook-ом все немного хитрее. Для того что бы законнектиться в Facebook мы размещаем на нашу страничку status.jsp кнопку «Connect to Facebook» используя следующий код:
<form id="fb_signin" action="<c:url value="/connect/facebook" />" method="post">
<div class="formInfo">
</div>
<div id="fb-root"></div>
<p><fb:login-button perms="email,publish_stream,offline_access" onlogin="$('#fb_signin').submit();" v="2" length="long">Connect to Facebook</fb:login-button></p>
</form>
<facebook:init />
Тут важно обратить внимание на следующие моменты:
* facebook:init тег реализован в библиотеке spring-social и ему требуется, что бы в спринговом контексте был бин ${facebookProvider} — у нас он реализован в классе FacebookProvider. По сути дела из этого бина тег берет только ключи;
* fb:login-button генерит кнопку «Connect to Facebook»;
* form-action /connect/facebook будет использован в качестве callback-а в случае успешного выполнения логина.
Так же для того что бы этот код заработал необходимо подключить tagLib
<%@ taglib uri="http://www.springframework.org/spring-social/facebook/tags" prefix="facebook" %>
и подключить jQuery — в моем случае я просто сделал
Ну и надо написать обработчик callback-а, который придет на /connect/facebook:
@RequestMapping(value="/connect/facebook", method=RequestMethod.POST)
public String connectAccountToFacebook(@FacebookAccessToken String accessToken,
@FacebookUserId String facebookUserId) {
if (facebookUserId != null && accessToken != null) {
// store facebook information
String userName = getCurrentUser().getName();
userService.updateFacebookAuthentication(userName, accessToken, facebookUserId);
}
return "redirect:/status";
}
* This source code was highlighted with Source Code Highlighter.
В данном примере мы увидим две интересные аннотации: @FacebookAccessToken и @FacebookUserId — это одна из фичек spring-social, но что бы она работала, нам необходимо добавить в проект специальный WebArgResolver. Для этого добавляем в applicationContext.xml:
<bean id="facebookWebArgResolver" class="org.springframework.social.facebook.FacebookWebArgumentResolver">
<constructor-arg name="apiKey" value="${facebook.appId}"/>
</bean>
* This source code was highlighted with Source Code Highlighter.
Обработка callback-а аналогична Tiwtter & Linkedin — сохраняем ключи (в нашем случае facebookUserId & facebookToken) для дальнейшего использования.
Отправка статуса в Twitter & Facebook
Законнектиться-законнектились, теперь можно и статус обновлять. На страничке статус у нас есть textArea где пользователь вводит текст и отправляет сообщение. Обработчик формы выглядит следующим образом:
@RequestMapping(value = "/status", method = RequestMethod.POST)
public String sendStatus(@Valid StatusForm statusForm, BindingResult result, ModelMap modelMap) {
User user = getCurrentUser();
LinkedInTemplateExt linkedInTemplate = linkedInProvider.createTemplate(user);
FacebookTemplate facebookTemplate = facebookProvider.createTemplate(user);
TwitterTemplate twitterTemplate = twitterProvider.createTemplate(user);
// send message to LinkedIn
if (linkedInTemplate != null) {
linkedInTemplate.updateStatus(statusForm.getStatus());
}
// send message to Facebook
if (facebookTemplate != null) {
facebookTemplate.updateStatus(statusForm.getStatus());
}
// send message to Twitter
if (twitterTemplate != null) {
twitterTemplate.updateStatus(statusForm.getStatus());
}
return "redirect:/status";
}
* This source code was highlighted with Source Code Highlighter.
В данном обработчике мы получаем FacebookTemplate & TwitterTemplate (оба класса из spring-social) используя ключи текущего пользователя и вызываем их метод updateStatus
Отправка статуса в LinkedIn
С LinkedIn-ом получилось немного сложней — тот LinkedInTemplate, которые реализован в spring-social не содержит метода для обновления статуса — так что нам придется написать его самим (используя REST-API вызов api.linkedin.com/v1/people/~/person-activities). Для этого мы пишем класс LinkedInTemplateExt — наследник «оригинального» LinkedInTemplate в котором:
* Инициализируем собственный объект класса RestOperations — при этом используется специальный OAuth1RequestSignerFactory.getRequestSigner для «правильного» кодирования вызовов (еще одна фича spring-social);
* Реализуем сам метод updateStatus:
public void updateStatus(String message) {
LinkedInPersonActivity personActivity = new LinkedInPersonActivity(message);
restOperationsExt.postForLocation("http://api.linkedin.com/v1/people/~/person-activities", personActivity);
}
* This source code was highlighted with Source Code Highlighter.
нам так же потребуется написать класс LinkedInPersonActivity — для передачи статуса:
@XmlRootElement(name = "activity")
public class LinkedInPersonActivity {
public LinkedInPersonActivity() {
}
public LinkedInPersonActivity(String body) {
this.body = body;
}
@XmlElement(name = "content-type")
String contentType = "linkedin-html";
@XmlElement(name = "body")
String body;
}
* This source code was highlighted with Source Code Highlighter.
Все — метод готов — как видно — не смотря на то что LinkedInTemplate изначально поддерживает очень мало функций из API — добавление новых функций не такая уж и сложная задача.
Вот вроде и все — надеюсь этот пример окажется полезным.