
用spring social构建API的client端
介绍H1
Spring Social可以用来连接Software-as-a-Service (SaaS) 服务商提供了REST API,Spring的项目本身已经提供了对Facebook, Twitter, Linkedin, Github等服务商的REST API的连接。
实际在开发app的过程中,通常是需要本地app连接到后台服务器的REST API的。客户端的开发本身可能还会涉及相对复杂的验证机制,比如OAuth Dance(客户端和服务器来回交互获取access token的过程),Spring Social为客户端的开发提供了一套框架,包括连接的建立,对API的强类型绑定,应用可以直接通过客户端访问需要的API。
术语H1
spring-social-core模块提供了一套Service Provider Connect Framework框架来实现到Software-as-a-Service (SaaS) 提供商的连接。
核心APIH2
ConnectionH3
Connection接口定义了连接到外部service provider的模型
bash
public interface Connection<A> extends Serializable {ConnectionKey getKey();String getDisplayName();String getProfileUrl();String getImageUrl();void sync();boolean test();boolean hasExpired();void refresh();UserProfile fetchUserProfile();void updateStatus(String message);A getApi();ConnectionData createData();}
通过这个Connection可以获取到关于这个连接的一些元数据信息。最主要的是可以通过Connection#getApi()获取到远程API的本地Java binding实例。
建立connectionsH2
本地用户和service provider的连接的建立是通过ServiceProvider来完成的。每个认证协议都作为它的一个具体的实现,这把协议相关的内容隔离出了核心API。ConnectionFactory封装了使用特定的认证协议对Connection的构建。
###OAuth2 service providers
OAuth2ConnectionFactory用来建立基于OAuth2服务的连接
bash
public class OAuth2ConnectionFactory<A> extends ConnectionFactory<A> {public OAuth2Operations getOAuthOperations();public Connection<A> createConnection(AccessGrant accessGrant);public Connection<A> createConnection(ConnectionData data);public void setScope(String scope);public String getScope();public String generateState();public boolean supportsStateParameter();}
getOAuthOperations()返回一个API用来执行验证流程OAuth Dance。这个流程的最终结果是需要得到一个AccessGrant,从而能够通过调用createConnection()建立connection.
OAuth2Operations接口定义如下:
bash
public interface OAuth2Operations {String buildAuthorizeUrl(OAuth2Parameters parameters);String buildAuthorizeUrl(GrantType grantType, OAuth2Parameters parameters);String buildAuthenticateUrl(OAuth2Parameters parameters);String buildAuthenticateUrl(GrantType grantType, OAuth2Parameters parameters);AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,MultiValueMap<String, String> additionalParameters);AccessGrant exchangeCredentialsForAccess(String username, String password,MultiValueMap<String, String> additionalParameters);AccessGrant refreshAccess(String refreshToken,MultiValueMap<String, String> additionalParameters);AccessGrant authenticateClient();AccessGrant authenticateClient(String scope);}
OAuth2的认证流程请参考,实际认证的流程类似:
bash
FacebookConnectionFactory connectionFactory =new FacebookConnectionFactory("clientId", "clientSecret");OAuth2Operations oauthOperations = connectionFactory.getOAuthOperations();OAuth2Parameters params = new OAuth2Parameters();params.setRedirectUri("https://my-callback-url");String authorizeUrl = oauthOperations.buildAuthorizeUrl(params);response.sendRedirect(authorizeUrl);// upon receiving the callback from the provider:AccessGrant accessGrant = oauthOperations.exchangeForAccess(authorizationCode, "https://my-callback-url", null);Connection<Facebook> connection = connectionFactory.createConnection(accessGrant);
开发H1
项目结构H2
Spring Social建议客户端模块以org.springframework.social.{providerId}为基础命名空间,比如说facebook的命名空间org.springframework.social.facebook。
在命名空间内部,推荐以下的子包目录结构:
| 子包 | 描述 |
|---|---|
| api | 定了API绑定的公共接口 |
| api.impl | API绑定接口的实现 |
| connect | 建立到service providerd的连接所需要的类 |
开发provider’s API的Java bindingH2
设计Java API bindingH3
对于API绑定的涉及和实现,开发者是有完全的控制权的,但是框架对提高代码的一致性和质量方面提供了一些指导方针。
- 支持API绑定接口和实现分离
- 支持RESTful资源风格的方式组织分层的API binding
下面是Twitter API Binding的例子:
bash
public interface Twitter extends ApiBinding {BlockOperations blockOperations();DirectMessageOperations directMessageOperations();FriendOperations friendOperations();GeoOperations geoOperations();ListOperations listOperations();SearchOperations searchOperations();StreamingOperations streamingOperations();TimelineOperations timelineOperations();UserOperations userOperations();RestOperations restOperations();}public interface DirectMessageOperations {List<DirectMessage> getDirectMessagesReceived();List<DirectMessage> getDirectMessagesReceived(int page, int pageSize);List<DirectMessage> getDirectMessagesReceived(int page, int pageSize, long sinceId, long maxId);List<DirectMessage> getDirectMessagesSent();List<DirectMessage> getDirectMessagesSent(int page, int pageSize);List<DirectMessage> getDirectMessagesSent(int page, int pageSize, long sinceId, long maxId);DirectMessage getDirectMessage(long id);void sendDirectMessage(String toScreenName, String text);void sendDirectMessage(long toUserId, String text);void deleteDirectMessage(long messageId);}
实现 Java API bindingH3
API开发者对于Java API binding的实现是完全自由的。Spring Social现有的API Binding实现spring-social-twitter使用了Spring Framework’s RestTemplate作为REST client;使用Jackson JSON ObjectMapper对Json数据进行序列化;使用Apache HttpComponents作为http客户端。
Spring Social推荐采用API的实现类命名为”{ProviderId}Template”的惯例。
API binding的实现类的构造方法根据不同的认证协议可能会不同,比如: OAuth1
bash
public TwitterTemplate(String consumerKey, String consumerSecret, String accessToken,String accessTokenSecret) { ... }
OAuth2
bash
public FacebookTemplate(String accessToken) { ... }
对API服务器的每个请求中需要用认证证书签名(比如,请求头中带入acess token),这需要在API binding的绑定构建中完成。实际上签名的过程就是,在每个客户端请求执行之前,添加 “Authorization”头到请求的过程。这个签名过程各个协议的不同,实现的方式也不同。为了封装这些复杂性逻辑,Spring Social为每个认证协议提供了ApiTemplate基类以供继承。比如,
OAuth1:
bash
public class TwitterTemplate extends AbstractOAuth1ApiBinding {public TwitterTemplate(String consumerKey, String consumerSecret, String accessToken,String accessTokenSecret) {super(consumerKey, consumerSecret, accessToken, accessTokenSecret);}}
OAuth2:
bash
public class FacebookTemplate extends AbstractOAuth2ApiBinding {public FacebookTemplate(String accessToken) {super(accessToken);}}
配置完成后,就可以通过调用getRestTemplate(),然后实现不同的API调用。
bash
public TwitterProfile getUserProfile() {return getRestTemplate().getForObject(buildUri("account/verify_credentials.json"),TwitterProfile.class);}
创建ServiceProvider模型H2
API bing的过程中需要提供有效的认证证书,证书的获取是通过让应用执行一个认证流程(“authorization dance”)来获取的。Spring Social提供ServiceProvider<A>抽象来处理”authorization dance”。
由于”authorization dance”是特定于协议的,对于每个认证协议都会有特定的ServiceProvider实现。
OAuth2H3
实现基于OAuth2的ServiceProvider,创建AbstractOAuth2ServiceProvider的子类,并且命名为{ProviderId}ServiceProvider。
bash
public final class FacebookServiceProvider extends AbstractOAuth2ServiceProvider<Facebook> {public FacebookServiceProvider(String clientId, String clientSecret) {super(new OAuth2Template(clientId, clientSecret,"https://graph.facebook.com/oauth/authorize","https://graph.facebook.com/oauth/access_token"));}public Facebook getApi(String accessToken) {return new FacebookTemplate(accessToken);}}
在构造方法中,需要调用super方法,传入实现了OAuth2Operations的OAuth2Template。实际是由OAuth2Template来处理
“OAuth dance”的。
创建ApiAdapterH2
Connection的职责是一是提供连接的用户账户的通用的,跨service providers的抽象。ApiAdapter的角色则是映射provider的本地API接口到统一的Connection模型上。connection代理给ApiAdapter来执行操作,例如,验证API证书,设置元数据等等。
bash
public interface ApiAdapter<A> {boolean test(A api);void setConnectionValues(A api, ConnectionValues values);UserProfile fetchUserProfile(A api);void updateStatus(A api, String message);}
org.springframework.social.twitter.connect.TwitterAdapter是它的一个实现:
bash
public class TwitterAdapter implements ApiAdapter<Twitter> {public boolean test(Twitter twitter) {try {twitter.userOperations().getUserProfile();return true;} catch (ApiException e) {return false;}}public void setConnectionValues(Twitter twitter, ConnectionValues values) {TwitterProfile profile = twitter.userOperations().getUserProfile();values.setProviderUserId(Long.toString(profile.getId()));values.setDisplayName("@" + profile.getScreenName());values.setProfileUrl(profile.getProfileUrl());values.setImageUrl(profile.getProfileImageUrl());}public UserProfile fetchUserProfile(Twitter twitter) {TwitterProfile profile = twitter.userOperations().getUserProfile();return new UserProfileBuilder().setName(profile.getName()).setUsername(profile.getScreenName()).build();}public void updateStatus(Twitter twitter, String message) {twitter.timelineOperations().updateStatus(message);}}
创建ConnectionFactoryH2
现在,已经完成了provider’s API的API binding,ServiceProvider实现了”authorization dance”,ApiAdapter实现了统一Connection模型的映射。最后需要创建ConnectionFactory用来包装这些东西,并且提供一个简单的接口来建立Connection。
类似ServiceProvider,ConnectionFactory对于不同的协议也有不同的实现。
OAuth2H3
创建OAuth2ConnectionFactory的子类命名为{ProviderId}ConnectionFactory。在构造方法中调用super,传入providerId,{ProviderId}ServiceProvider实例以及{Provider}Adapter实例
bash
public class FacebookConnectionFactory extends OAuth2ConnectionFactory<Facebook> {public FacebookConnectionFactory(String clientId, String clientSecret) {super("facebook", new FacebookServiceProvider(clientId, clientSecret), new FacebookAdapter());}}
测试Java API bindingH2
这里进行测试的主要目的是保证调用API接口方法是,进行了正确的绑定了,请求了正确了URL,设置了正确的请求参数,服务器返回的数据得到了正确的转换。
这里需要引入类MockRestServiceServer:
bash
TwitterTemplate twitter = new TwitterTemplate("consumerKey", "consumerSecret", "accessToken","accessTokenSecret");MockRestServer mockServer = MockRestServiceServer.createServer(twitter.getRestTemplate());
MockRestServiceServer会记录API Binding的请求,并提供了方法来验证。
bash
@Testpublic void getUserProfile() {HttpHeaders responseHeaders = new HttpHeaders();responseHeaders.setContentType(MediaType.APPLICATION_JSON);mockServer.expect(requestTo("https://api.twitter.com/1.1/account/verify_credentials.json")).andExpect(method(GET)).andRespond(withSuccess(jsonResource("twitter-profile"), APPLICATION_JSON));TwitterProfile profile = twitter.userOperations().getUserProfile();assertEquals(161064614, profile.getId());assertEquals("jbauer", profile.getScreenName());}
扩展ClientHttpRequestInterceptorH2
RestTemplate可以通过配置拦截器,对请求进行拦截、包装HttpRequest,对请求封装一些共同的逻辑,例如,API的Accept头中设置
MediaType或者API Version头等等。拦截器需要实现ClientHttpRequestInterceptor接口方法:
bash
ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;
其中
HttpRequestWrappers是HttpRequest实现类的包装类HttpRequestDecorator继承自HttpRequestWrappers,是个装饰类,可以利用装饰者模式对HttpRequest进行扩展。例如:bash
@Overridepublic ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {HttpRequest protectedResourceRequest = new HttpRequestDecorator(request);protectedResourceRequest.getHeaders().add("Accept", mediaType.toString());return execution.execute(new HttpRequestDecorator(request) {@Overridepublic URI getURI() {URI uri = super.getURI();URI baseUri = null;try {baseUri = new URI(URL_API);} catch (URISyntaxException e) {}return baseUri.resolve(uri);}}, body);}
总结H1
以OAuth2的为例
- 应用实例化
OAuth2ConnectionFactory,ConnectionFactory是对API binding,ServiceProvider,ApiAdapter的包装 ConnectionFactory调用getOAuthOperation获取接口,用于进行OAuth2授权getOAuthOperation返回的实例实际是由ServiceProvider提供的,ServiceProvider负责”authorization dance”,框架提供了AbstractOAuth2ServiceProvider实现OAuth2的验证流程- 验证流程最终的目的是获取
GrantAccess,用户建立Connection - 传递
GrantAccess给OAuth2ConnectionFactory#createConnection创建Connection Connection接口提供了一些接口获取远程用户的元数据信息,实际是代理给ApiAdapter来实现调用的,目的是为了保持Connection统一的接口模型Connection#getApi获取API binding,供应用调用远程接口。- API binding的实现最好接口和实现分离,接口最好分层定义。接口的实现根据授权协议的不同,继承不同的基类,OAuth2继承
AbstractOAuth2ApiBinding,基类主要是作用是能够返回配置好的RestTemplate以供实现进行远程调用,配置RestTemplate主要是要根据授权协议,设置好请求头的参数。
评论
新的评论
上一篇
对于OAuth2.0授权方式的理解
概述 OAuth2.0的最好的文档,莫过于 RFC 6749 。OAuth2.0本身是一个比较灵活的标准,能够适配应用到各种场景。本文主要是对文档的翻译以及加上部分个人的理解。 角色 OAuth定义了4个角色 resource owner 能够对受保护的资源进行授权的实体。…
下一篇
Spring XD Type Conversion
Spring Integration 的 Type Conversion Datatype Channel Datatype Channel 可以指定Channel接收的数据的数据类型 指定了 datatype 之后,如果Channel收到了类型不是指定的类型,会如下进行: 查…
