虽然REST架构是Roy Fielding(他也是HTTP和URI规范的主要作者之一)在一篇论文中描述的,没有被官方指定相关的标准,但在REST服务大行其道的今天,其实已经成为事实的开发选型过程中的首选。
REST服务能够大行其道,有一定原因在里头。对系统层级关系的减负之类我们本次不表,单说对团队角色职责清晰化带来的影响,比如,交互采用JSON串,这样就可以很方便的通过js技术以最少的编码量来使用,这样一来,使原来前后端界限不明的前端页面从后端开发同学这里完全释放出来。UI端与API端已经从职业的角度划分开来。因此,REST服务的到来使前端开发同学与后端开发同学的精力能够更加聚焦,更加专注于处理自己擅长的工作。
为确保服务通信安全,通常会使用https服务。但问题也跟随来了,对于https方式的rest服务,如何集成到我们系统服务中来。如果直接还是使用默认的http方式集成进来,那https提供的证书怎么支持,下面我们将说明如何在访问https方式的rest服务时,将相关的证书信息一并处理,确保对接业务的正常执行。
spring提供了一个模板 org.springframework.web.client.RestTemplate 用来在服务端进行REST服务的对接工作。RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。
ClientHttpRequestFactory接口主要提供了两种实现方式
- 一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。
- 一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。
有了这些常识的预热,接下来的做法就很好办了,为方便集成过程中的使用,我们把通过HttpComponentsClientHttpRequestFactory类构建RestTemplate过程整理为一个工具类:
/**
* Danlley Wei (mailto://danlley@126.com)
* Copyright (c) 2005-2017 All Rights Reserved.
*/
package com.myteay.restful.mapper;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.log4j.Logger;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* HttpClient证书处理工具类
*
* @author danlley(danlley@126.com)
* @version $Id: HttpClientUtils.java, v 0.1 2017年4月22日 下午8:40:55 danlley(danlley@126.com) Exp $
*/
public class HttpClientUtils {
/** 日志 */
private static final Logger logger = Logger.getLogger(HttpClientUtils.class);
private static final HttpClientUtils _instance = new HttpClientUtils();
public static HttpClientUtils getInstance() {
return _instance;
}
/**
* 初始化证书支持的CloseableHttpClient
*
* @return CloseableHttpClient
* @throws KeyStoreException 证书存储异常
* @throws NoSuchAlgorithmException 算法异常
* @throws KeyManagementException 证书管理异常
*/
public static CloseableHttpClient acceptsUntrustedCertsHttpClient() throws KeyStoreException,
NoSuchAlgorithmException,
KeyManagementException {
//不做任何处理,默认相信所有证书
SSLContext sslContext = new SSLContextBuilder()
.loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] arg0,
String arg1) throws CertificateException {
return true;
}
}).build();
//将对证书的认可配置加入上下文
HttpClientBuilder b = HttpClientBuilder.create();
b.setSSLContext(sslContext);
//对HostName不做校验
HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE;
//注册请求方式
SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext,
hostnameVerifier);
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
.<ConnectionSocketFactory> create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslSocketFactory).build();
//将请求方式加入连接管理器(支持多线程)
PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(
socketFactoryRegistry);
connMgr.setMaxTotal(200);
connMgr.setDefaultMaxPerRoute(100);
b.setConnectionManager(connMgr);
return b.build();
}
/**
* 构建RestTemplate实例
*
* @return RestTemplate
*/
public static RestTemplate buildRestTemplate() {
CloseableHttpClient httpClient = null;
try {
httpClient = acceptsUntrustedCertsHttpClient();
} catch (KeyManagementException e) {
logger.error("证书关键字异常:" + e.getMessage(), e);
} catch (KeyStoreException e) {
logger.error("证书存储异常:" + e.getMessage(), e);
} catch (NoSuchAlgorithmException e) {
logger.error("算法执行异常:" + e.getMessage(), e);
}
return new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
}
/**
* 请求指定地址的restful服务
*
* @param url
* @param obj
* @return
*/
public Object postRestfulService(String url, Object obj) {
if (obj == null) {
logger.warn("入参对象不可用,不做请求。 obj is null!");
return null;
}
RestTemplate template = buildRestTemplate();
return template.postForObject(url, obj, obj.getClass());
}
}
接下来编写使用代码:
@RequestMapping(value = "/greet", method = { RequestMethod.POST })
public ApplicationModel greeting(@RequestBody ApplicationModel model,
HttpServletRequest request, HttpServletResponse response) {
logger.warn("------------------->model=" + model);
Object quote = HttpClientUtils.getInstance()
.postRestfulService("https://192.168.56.102:8080/greet", model);
logger.warn("++++++++++++++++ read from server quote: " + quote);
return (ApplicationModel) quote;
}
执行结果如下:
2017-04-22 21:59:56.195 WARN 27752 --- [nio-8080-exec-4] c.m.r.service.impl.RestfulController : ------------------->model=ApplicationModel[keyModel=ObjectModel[id=test,content=danlley-55---],key=my,value=alipay] 2017-04-22 21:59:56.592 WARN 27752 --- [nio-8080-exec-4] c.m.r.service.impl.RestfulController : ++++++++++++++++ read from server quote: ApplicationModel[keyModel=ObjectModel[id=test,content=danlley-55---],key=my,value=alipay]