抢先一步
VMware 提供培训和认证,为您的进步加速。
了解更多上周,我向大家介绍了 Spring Social 的服务提供商“连接”框架,并展示了它如何简化用户本地应用账户与他们在 SaaS(软件即服务)提供商上的账户之间的连接创建。今天我想向大家展示如何扩展服务提供商框架,以处理与 Spring Social 不直接支持的提供商的连接。
假设您正在开发一个电影评论网站,用户可以在其中阅读和发布短篇电影评论。通常,电影评论会按最新条目优先显示在首页上。但如果用户已将其账户连接到其 Netflix 账户,则您可以向他们显示其 Netflix 光盘队列中电影的评论。为了实现这一点,您希望利用 Spring Social 的服务提供商框架来连接您的用户账户与其 Netflix 账户。Spring Social 1.0.0.M2 不包含 Netflix 服务提供商或 API 绑定,但可以轻松扩展以与不直接支持的提供商配合使用。
在本文中,我将向您展示如何基于 Spring Social 的服务提供商框架构建,以实现与 Netflix 的连接。我们将首先开发一个 Netflix 服务提供商实现,然后构建一个简单的 API 绑定来支持我们应用程序的需求。用于开发 Netflix 服务提供商的技术可以应用于扩展 Spring Social 以支持几乎任何服务提供商。您可以通过查看 GitHub 上的示例代码 来跟随本文。
在我们开始开发 Netflix 服务提供商实现之前,我们需要进行一些前期的研究,了解 Netflix 授权 API 的一些基本细节是如何工作的。
我们需要确定的第一件事是 Netflix 使用什么授权协议。Netflix API 文档的身份验证概述 部分告诉我们他们使用 OAuth,但没有明确说明使用的是哪个版本的 OAuth 规范。因此,需要进行一些侦探工作。
向下滚动页面一点(在“那些令人讨厌的 OAuth 参数”标题下),我们看到了消费者密钥、随机数和时间戳。这些东西不适用于 OAuth 2,所以 Netflix 肯定是 OAuth 1 提供商。此外,对 oauth_version
参数设置为“1.0”的描述进一步证实了 Netflix 实现了 OAuth 1。
现在我们知道 Netflix 使用 OAuth 1。但知道他们是实现了规范的 1.0 版本还是 1.0a 版本也很重要。服务提供商通常不会在文档中详细说明这一点,并且在任何一种情况下,oauth_version
的值都应该是“1.0”。然而,有一些明显的迹象可以指向特定的 OAuth 规范版本。以下是一些表明使用的是 OAuth 1.0 的线索:
oauth_callback
参数在授权 URL 中发送,而不是在请求令牌请求中发送。oauth_verifier
参数。对于 OAuth 1.0a,请注意以下迹象:
oauth_callback
参数在请求令牌请求中发送,而不是在授权 URL 中发送。oauth_verifier
参数。在 Netflix 文档中查找这些线索后,我们确定 Netflix 使用的是 OAuth 1.0(而不是 1.0a)。此信息非常重要,对于我们定义服务提供商实现将很有用。
最后,我们需要知道请求令牌 URL、授权 URL 和访问令牌 URL 是什么。再往下一点(在“发出受保护调用”标题下),您会找到详细信息,告诉我们所需的 URL 如下所示:
特别注意请求和访问令牌 URL 中使用的协议。大多数提供商在这方面很灵活,推荐使用 https。但是,根据我使用 Netflix 的经验,如果您通过 https 请求请求或访问令牌,Netflix 会投诉请求签名无效。不过,授权 URL 通过 https 工作正常。
要创建新的服务提供商实现,我们需要扩展 AbstractOAuth1ServiceProvider
或 AbstractOAuth2ServiceProvider
。这两个类分别为 OAuth 1.0/1.0a 和 OAuth 2 提供 OAuth 版本特定的基础功能。由于 Netflix 是一个 OAuth 1.0 提供商,我们的 NetFlixServiceProvider
需要扩展 AbstractOAuth1ServiceProvider
。
package org.springframework.social.movies.netflix;
import org.springframework.social.connect.oauth1.AbstractOAuth1ServiceProvider;
import org.springframework.social.connect.support.ConnectionRepository;
import org.springframework.social.oauth1.OAuth1Template;
public final class NetFlixServiceProvider extends AbstractOAuth1ServiceProvider<NetFlixApi> {
public NetFlixServiceProvider(String consumerKey, String consumerSecret, ConnectionRepository connectionRepository) {
super("netflix", connectionRepository, consumerKey, consumerSecret,
new OAuth1Template(consumerKey, consumerSecret,
"http://api.netflix.com/oauth/request_token",
"https://api-user.netflix.com/oauth/login?oauth_token={requestToken}" +
"&oauth_callback={redirectUri}&oauth_consumer_key=" + consumerKey,
"http://api.netflix.com/oauth/access_token",
OAuth1Version.CORE_10));
}
@Override
protected NetFlixApi getApi(String consumerKey, String consumerSecret, String accessToken, String secret) {
return new NetFlixTemplate(consumerKey, consumerSecret, accessToken, secret);
}
}
在扩展 Spring Social 的抽象服务提供商类时,您必须做两件事:在构造函数中设置提供商特定信息并实现 getApi()
方法。
抽象基类包含与服务提供商连接的所有机制。但您必须通过将提供商特定信息传递给 super()
构造函数来设置它。在这里,NetFlixServiceProvider
构造函数调用 super()
构造函数,传递“netflix”作为提供商 ID,以及给定的连接仓库、消费者密钥和消费者密文,以及用于与提供商协商身份验证的 OAuth1Template
实例。
这里给出的 OAuth1Template
是用消费者密钥和密文构建的,并且还给了我们在初步研究中收集到的三个 URL(请求令牌、授权和访问令牌)。请注意,授权 URL 已参数化,以便接受请求令牌和重定向 URI。ConnectController
将在授权流程过程中提供这些详细信息。另请注意,授权 URL 也接受 oauth_consumer_key
参数。这似乎是 Netflix 特有的要求;OAuth 1.0 规范没有这样的要求,我也没有遇到过任何其他提供商需要它。
大多数 OAuth 1 服务提供商都实现了 OAuth 1.0a 规范。因此,OAuth1Template
默认假定它将处理 OAuth 1.0a。然而,Netflix 是一个基于 OAuth 1.0 的提供商。传递给 OAuth1Template
构造函数的最后一个参数指定它不应该假定 1.0a,而应该根据 OAuth 1.0 条款与提供商协商。如果 Netflix 是一个 OAuth 1.0a 提供商,这个参数可以设置为 OAuth1Version.CORE_10_REVISION_A
或完全省略。
服务提供商实现的另一项要求是实现 getApi()
方法。对于 OAuth 1 提供商,此方法接受四个 String
参数,其中包含应用程序的消费者密钥/密文对和访问令牌/密文对。在这里,这些值用于创建并返回 NetFlixTemplate
的新实例(稍后会详细介绍这个类)。
尽管 NetFlixServiceProvider
仅演示了如何为 OAuth 1 开发服务提供商实现,但在扩展 AbstractOAuth2ServiceProvider
以创建 OAuth 2 服务提供商时,模型并没有太大不同。主要区别在于:
super()
构造函数向上传递。OAuth2Template
实例而不是 OAuth1Template
(并且不需要请求令牌 URL)。getApi()
方法只提供访问令牌值用于构造 API 绑定。请查看 FacebookServiceProvider
、GitHubServiceProvider
或 GowallaServiceProvider
,以获取如何创建基于 OAuth 2 的服务提供商实现的示例。要查看更多 OAuth 1 服务提供商的示例,您可能还需要查看 TwitterServiceProvider
、LinkedInServiceProvider
和 TripItServiceProvider
。
服务提供商实现完成后,现在我们将注意力转向创建与 Netflix REST API 的绑定。为了满足我们的即时需求,我们需要一种方法来读取用户的光盘队列。为了定义该操作,我们创建了定义服务 API 的 NetFlixApi
接口:
public interface NetFlixApi {
List<CatalogTitle> searchForTitles(String searchTerms);
List<QueueItem> getDiscQueue();
}
这并不是与 Netflix REST API 的完整绑定。但这足以满足我们的目的。searchForTitles()
方法可用于帮助用户选择他们想写评论的电影。而 getDiscQueue()
方法将用于检索用户光盘队列中的项目。现在我们需要创建一个实现类。NetFlixTemplate
使用 Spring 的 RestTemplate
来调用 Netflix REST API:
package org.springframework.social.netflix;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.social.oauth1.ProtectedResourceClientFactory;
import org.springframework.web.client.RestTemplate;
public class NetFlixTemplate implements NetFlixApi {
private final RestTemplate restTemplate;
private final String userBaseUrl;
public NetFlixTemplate(String apiKey, String apiSecret, String accessToken,
String accessTokenSecret) {
this.restTemplate =
ProtectedResourceClientFactory.create(apiKey, apiSecret, accessToken, accessTokenSecret);
this.userBaseUrl = getUserBaseUrl();
}
public List<CatalogTitle> searchForTitles(String searchTerm) {
Map<String, Object> resultMap = restTemplate.getForObject(SEARCH_TITLES_URL, Map.class, searchTerm);
List<CatalogTitle> titles = new ArrayList<CatalogTitle>();
// extract CatalogTitle objects from resultMap
return titles;
}
public List<QueueItem> getDiscQueue() {
Map<String, Object> resultMap = restTemplate.getForObject(userBaseUrl + QUEUE_PATH, Map.class);
List<QueueItem> queueItems = new ArrayList<QueueItem>();
// extract QueueItem objects from resultMap
return queueItems;
}
private String getUserBaseUrl() {
Map<String, Map<String, Map<String, String>>> result =
restTemplate.getForObject(CURRENT_USER_URL, Map.class);
return result.get("resource").get("link").get("href");
}
private static final String SEARCH_TITLES_URL =
"http://api.netflix.com/catalog/titles?term={term}&max_results=5&output=json";
private static final String CURRENT_USER_URL =
"http://api.netflix.com/users/current?output=json";
private static final String QUEUE_PATH = "/queues/disc?output=json";
}
请注意,尽管 NetFlixTemplate
使用 RestTemplate
,但它并没有为自己创建一个 RestTemplate
实例。相反,它使用 ProtectedResourceClientFactory
来创建支持 OAuth 的 RestTemplate
实例。由 ProtectedResourceClientFactory
创建的 RestTemplate
将被设置为使用 OAuth 凭据在其发出的每个请求中添加一个“Authorization”头部进行签名。
searchForTitles()
和 getDiscQueue()
都使用支持 OAuth 的 RestTemplate
来执行针对 Netflix REST API 的各自操作。URL 中的 output
参数告诉 Netflix API 我们更喜欢接收 JSON 响应而不是 XML。在每种情况下,对 getForObject()
的调用都返回一个反映 JSON 响应结构的 Map
。然后从 Map
中提取相关信息以生成返回给调用者的列表。(为了简洁起见,我省略了上面列表中如何分解 Map
的详细信息。请在 GitHub 上查看 NetFlixTemplate
的完整实现。)
Netflix REST API 中所有面向用户的操作,包括检索用户光盘队列的调用,其 URL 都以 "http://api.netflix.com/users/{user ID}" 开头。尽管 NetFlixTemplate
无法直接获得用户的 Netflix ID,但可以通过 "/users/current" API 调用检索到用户的基本 URL(包括其 Netflix ID)。getUserBaseUrl()
方法调用 "/users/current" 来检索用户的基本 URL。为了避免在每次调用之前都检索基本 URL,构造函数会调用 getUserBaseUrl()
方法一次,并将基本 URL 存储在成员变量中,以便稍后在构建面向用户的操作的 URL 时使用。
现在我们有了 Netflix 服务提供商和 API 绑定,我们可以围绕它们构建电影评论应用程序的其余部分。作为 getDiscQueue()
方法如何使用的示例,请看以下截图的右侧栏:
在这里,显示了用户光盘队列中的电影列表以及这些电影的近期评论。此时,很容易想象对该应用程序进行进一步增强,也许可以允许用户在考虑其他用户的评论时修改他们的队列。
在 Netflix 示例中,我选择创建自己的 API 绑定。但是,如果已经存在您喜欢使用的绑定到服务提供商的现有库,那么完全可以使用它与提供商的 API 进行交互,同时利用 Spring Social 的服务提供商框架进行连接处理。
例如,尽管 Spring Social 提供了与 Twitter REST API 的 Java 绑定,但您可能更喜欢使用其他绑定实现,例如 Twitter4J。Twitter4J 提供了与 Twitter 服务 API 的全面 Java 绑定,但不处理授权流程或连接管理。如果您想使用 Twitter4J 的 API 以及 Spring Social 的连接管理功能,可以通过创建使用 Twitter4J 作为 API 绑定的服务提供商来实现。
要做到这一点,您需要创建一个服务提供商实现,其 getApi()
方法使用 TwitterFactory
来构造 Twitter4J 实例,而不是 TwitterTemplate
。以下是基于 Twitter4J 的服务提供商实现的样子:
package org.springframework.social.showcase.twitter;
import java.util.Properties;
import org.springframework.social.connect.oauth1.AbstractOAuth1ServiceProvider;
import org.springframework.social.connect.support.ConnectionRepository;
import org.springframework.social.oauth1.OAuth1Template;
import twitter4j.Twitter;
import twitter4j.TwitterFactory;
import twitter4j.conf.Configuration;
import twitter4j.conf.PropertyConfiguration;
public final class Twitter4JServiceProvider extends AbstractOAuth1ServiceProvider<Twitter> {
public Twitter4JServiceProvider(String consumerKey, String consumerSecret, ConnectionRepository connectionRepository) {
super("twitter", connectionRepository, consumerKey, consumerSecret, new OAuth1Template(consumerKey, consumerSecret,
"https://twitter.com/oauth/request_token",
"https://twitter.com/oauth/authorize?oauth_token={requestToken}",
"https://twitter.com/oauth/access_token"));
}
@Override
protected Twitter getApi(String consumerKey, String consumerSecret, String accessToken, String secret) {
Properties props = new Properties();
props.setProperty(PropertyConfiguration.OAUTH_CONSUMER_KEY, consumerKey);
props.setProperty(PropertyConfiguration.OAUTH_CONSUMER_SECRET, consumerSecret);
props.setProperty(PropertyConfiguration.OAUTH_ACCESS_TOKEN, accessToken);
props.setProperty(PropertyConfiguration.OAUTH_ACCESS_TOKEN_SECRET, secret);
Configuration conf = new PropertyConfiguration(props);
return new TwitterFactory(conf).getInstance();
}
}
正如您所见,Twitter4JServiceProvider
与 Spring Social 的 TwitterServiceProvider
非常相似,也与之前创建的 NetFlixServiceProvider
十分相似。主要区别在于 Twitter4JServiceProvider
被参数化为 Twitter
服务提供商,并且 getApi()
方法构造了一个 Twitter4J Twitter
实例。
Twitter4JServiceProvider
的代码以及使用它的示例可以在 GitHub 的 Spring Social Samples 仓库 中找到。
尽管 Spring Social 1.0.0.M2 专注于少数几个 SaaS 提供商,但服务提供商框架易于扩展,使您可以在 Spring Social 的基础上构建对其他提供商的支持。此外,该框架不仅限于开发针对 Spring Social 特定 API 绑定的服务提供商实现,您还可以使用它为现有 API 绑定创建连接。
既然谈到扩展 Spring Social,您可能想要探索的另一个领域是创建 ConnectionRepository
接口的新实现。Spring Social 1.0.0.M2 附带了一个 JDBC 支持的实现,但还有其他持久化连接的可能性。例如,Spring Android 项目定义了一个 SqliteConnectionRepository
,它允许将连接写入存储在 Android 设备本地的 SQLite 数据库。此外,看看 NoSQL 连接仓库会是什么样子会很有趣。
我们期待看到您如何扩展 Spring Social。如果您创建了对 Spring Social 有用或有趣的扩展,请在论坛中告诉我们,或者在 GitHub 中向我们发送拉取请求。我们已经收到了社区的一些拉取请求,并正在努力将它们整合到 Spring Social 中。非常感谢这些贡献!