RestTemplate踩坑
public RestTemplate init() throws Exception{ SSLConnectionSocketFactory connectionSocketFactory = initSSL(jks_path, jks_pwd, jks_path, jks_pwd);
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create().register("https", connectionSocketFactory)
.build());
int availableProcessors = Runtime.getRuntime().availableProcessors();
poolingConnectionManager.setMaxTotal(2 * availableProcessors + 3); // 连接池最大连接数
poolingConnectionManager.setDefaultMaxPerRoute(2 * availableProcessors); // 每个主机的并发
CloseableHttpClient httpclient = HttpClients.custom().setConnectionManager(poolingConnectionManager)
.disableAutomaticRetries().build();
HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(
httpclient);
clientHttpRequestFactory.setReadTimeout(anxinsignConfig.getReadTimeout());
clientHttpRequestFactory.setConnectionRequestTimeout(anxinsignConfig.getConnectionRequestTimeout());
clientHttpRequestFactory.setConnectTimeout(anxinsignConfig.getConnectTimeout());
RestTemplate restTemplate = new RestTemplate();
StringHttpMessageConverter httpMessageConverter = new StringHttpMessageConverter();
List<MediaType> list = Lists.newArrayList();
list.addAll(httpMessageConverter.getSupportedMediaTypes());
list.add(MediaType.TEXT_PLAIN);
httpMessageConverter.setSupportedMediaTypes(httpMessageConverter.getSupportedMediaTypes());
restTemplate.getMessageConverters().add(httpMessageConverter);
return restTemplate;
}
/**
* SSL的证书配置
*
* @param keyStorePath 证书地址
* @param keyStorePassword 证书密码
* @param trustStorePath 可以与keyStorePath相同
* @param trustStorePassword 可以与keyStorePassword相同
*/
public SSLConnectionSocketFactory initSSL(String keyStorePath, char[] keyStorePassword, String trustStorePath,
char[] trustStorePassword) throws Exception {
KeyManagerFactory keyManagerFactory = null;
KeyStore keyStore = null;
if (CommonUtil.isEmpty(sslConfig.keyProvider)) {
keyManagerFactory = KeyManagerFactory.getInstance(sslConfig.keyAlgorithm);
if (CommonUtil.isNotEmpty(sslConfig.keyStoreType)) {
keyStore = KeyStore.getInstance(sslConfig.keyStoreType);
}
} else {
keyManagerFactory = KeyManagerFactory.getInstance(sslConfig.keyAlgorithm, sslConfig.keyProvider);
if (CommonUtil.isNotEmpty(sslConfig.keyStoreType)) {
keyStore = KeyStore.getInstance(sslConfig.keyStoreType, sslConfig.keyProvider);
}
}
if (CommonUtil.isEmpty(keyStorePath)) {
keyManagerFactory.init(keyStore, keyStorePassword);
} else {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(keyStorePath);
keyStore.load(fileInputStream, keyStorePassword);
keyManagerFactory.init(keyStore, keyStorePassword);
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
}
TrustManagerFactory trustManagerFactory = null;
KeyStore trustStore = null;
if (CommonUtil.isEmpty(sslConfig.trustProvider)) {
trustManagerFactory = TrustManagerFactory.getInstance(sslConfig.trustAlgorithm);
if (CommonUtil.isNotEmpty(sslConfig.trustStoreType)) {
trustStore = KeyStore.getInstance(sslConfig.trustStoreType);
}
} else {
trustManagerFactory = TrustManagerFactory.getInstance(sslConfig.trustAlgorithm, sslConfig.trustProvider);
if (CommonUtil.isNotEmpty(sslConfig.trustStoreType)) {
trustStore = KeyStore.getInstance(sslConfig.trustStoreType, sslConfig.trustProvider);
}
}
if (CommonUtil.isEmpty(trustStorePath)) {
trustManagerFactory.init(trustStore);
} else {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(trustStorePath);
trustStore.load(fileInputStream, trustStorePassword);
trustManagerFactory.init(trustStore);
fileInputStream.close();
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
}
}
SSLContext sslContext = null;
if (CommonUtil.isEmpty(sslConfig.sslProvider)) {
sslContext = SSLContext.getInstance(sslConfig.sslProtocol);
} else {
sslContext = SSLContext.getInstance(sslConfig.sslProtocol, sslConfig.sslProvider);
}
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
return new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
}
public static class SSLConfig {
public String sslProvider = null;
public String sslProtocol = "TLSv1.1";
public String keyProvider = null;
public String keyAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
public String keyStoreType = KeyStore.getDefaultType();
public String trustProvider = null;
public String trustAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
public String trustStoreType = KeyStore.getDefaultType();
public boolean ignoreHostname = true;
}
踩坑
1. org.apache.http.impl.client.HttpClients.custom().build() -> org.apache.http.impl.client.HttpClientBuilder.build() 时注意: 对于org.apache.http.conn.ssl.SSLConnectionSocketFactory 如下图中的设置方式是失效的。
private SSLConnectionSocketFactory connectionSocketFactory;PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
int availableProcessors = Runtime.getRuntime().availableProcessors();
poolingConnectionManager.setMaxTotal(2 * availableProcessors + 3); // 连接池最大连接数
poolingConnectionManager.setDefaultMaxPerRoute(2 * availableProcessors); // 每个主机的并发
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(connectionSocketFactory).setConnectionManager(poolingConnectionManager).disableAutomaticRetries().build();
优先使用外部设置的HttpClientConnectionManager配置。 否则内部初始化新的PoolingHttpClientConnectionManager对象,这才会去使用外部设置的SSLSocketFactory(setSSLSocketFactory方法)
需要改写为:由外部指定的PoolingHttpClientConnectionManager直接注册SSLSocketFactory
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager( RegistryBuilder.<ConnectionSocketFactory>create().register("https", connectionSocketFactory)
.build());
CloseableHttpClient httpclient = HttpClients.custom()
.setConnectionManager(poolingConnectionManager).disableAutomaticRetries().build();
源码如下:
都是优先使用外部指定的配置,但需要注意使用的前提条件!!!
接着,在内部初始化PoolingHttpClientConnectionManager上注入支持https的SSL对象
2. 在使用SSLConnectionSocketFactory的过程中有可能会报错: “RSA premaster secret error” | “SunTlsRsaPremasterSecret KeyGenerator not available”
javax.net.ssl.SSLKeyException: RSA premaster secret errorres:RSA premaster secret error
at sun.security.ssl.RSAClientKeyExchange.<init>(RSAClientKeyExchange.java:87)
at sun.security.ssl.ClientHandshaker.serverHelloDone(ClientHandshaker.java:972)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:369)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:162)
at cfca.trustsign.demo.connector.HttpClient.send(HttpClient.java:155)
at cfca.trustsign.demo.connector.HttpConnector.deal(HttpConnector.java:111)
at cfca.trustsign.demo.connector.HttpConnector.post(HttpConnector.java:70)
at cfca.trustsign.demo.test.Test3401.main(Test3401.java:84)
Caused by: java.security.NoSuchAlgorithmException: SunTlsRsaPremasterSecret KeyGenerator not available
at javax.crypto.KeyGenerator.<init>(KeyGenerator.java:169)
at javax.crypto.KeyGenerator.getInstance(KeyGenerator.java:223)
at sun.security.ssl.JsseJce.getKeyGenerator(JsseJce.java:251)
at sun.security.ssl.RSAClientKeyExchange.<init>(RSAClientKeyExchange.java:78)
... 15 more
还可能报错: “Could not generate secret” | "ECDH key agreement requires ECPrivateKey for initialisation"
Caused by: javax.net.ssl.SSLHandshakeException: Could not generate secret at sun.security.ssl.ECDHCrypt.getAgreedSecret(ECDHCrypt.java:104)
at sun.security.ssl.ClientHandshaker.serverHelloDone(ClientHandshaker.java:1122)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:369)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:1037)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:965)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:396)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:355)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:359)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:381)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:237)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:185)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:111)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56)
at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:87)
at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48)
at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735)
... 5 common frames omitted
Caused by: java.security.InvalidKeyException: ECDH key agreement requires ECPrivateKey for initialisation
at cfca.sadk.org.bouncycastle.jcajce.provider.asymmetric.ec.KeyAgreementSpi.initFromKey(KeyAgreementSpi.java:194)
at cfca.sadk.org.bouncycastle.jcajce.provider.asymmetric.ec.KeyAgreementSpi.engineInit(KeyAgreementSpi.java:168)
at javax.crypto.KeyAgreement.implInit(KeyAgreement.java:346)
at javax.crypto.KeyAgreement.chooseProvider(KeyAgreement.java:378)
at javax.crypto.KeyAgreement.init(KeyAgreement.java:470)
at javax.crypto.KeyAgreement.init(KeyAgreement.java:441)
at sun.security.ssl.ECDHCrypt.getAgreedSecret(ECDHCrypt.java:100)
... 28 common frames omitted
还有:“unable to find valid certification path to requested target”
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:397)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:302)
at sun.security.validator.Validator.validate(Validator.java:262)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1621)
... 19 common frames omitted
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:141)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:126)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:392)
... 25 common frames omitted
将项目中jdk或jre改成自定义安装的就好,不要用eclipse自带的。
3. restTemplate使用时: 设置的org.springframework.http.converter.HttpMessageConverter需要与传递参数的Class匹配及org.springframework.http.HttpHeaders设置"Content-Type"的值MediaType匹配。
在 抽象类 org.springframework.http.converter.AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> 中 定义的canRead | canWrite 方法会判定supportedMediaTypes 及 supports的Class
给默认已经装载的HttpMessageConverter增加MediaType 需要如下文处理:
RestTemplate restTemplate = new RestTemplate();StringHttpMessageConverter httpMessageConverter = new StringHttpMessageConverter();
List<MediaType> list = Lists.newArrayList();
list.addAll(httpMessageConverter.getSupportedMediaTypes());
list.add(MediaType.TEXT_PLAIN);
httpMessageConverter.setSupportedMediaTypes(httpMessageConverter.getSupportedMediaTypes());
restTemplate.getMessageConverters().add(httpMessageConverter);
因为:getSupportedMediaTypes返回的是个不可变集合
4. org.springframework.web.client.RestTemplate.postForObject 传参 一定要使用 org.springframework.util.MultiValueMap 来封装参数,否则 无法传递参数。(应该与服务端接收请求的处理方式有关)
org.apache.http.client.methods.HttpPost 传参使用 org.apache.http.message.BasicNameValuePair
下文两种方式都可以传递
private void deal(String remoteUrl, HttpMethod method, String data, String signature) { try {
MultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<String, Object>();
paramMap.add("data", data);
paramMap.add("signature", signature);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setCacheControl(httpConfig.cache);
requestHeaders.setAccept(Arrays.asList(MediaType.valueOf(httpConfig.contentType)));
requestHeaders.setAcceptCharset((Arrays.asList(Charset.forName(AnxinSignConst.DEFAULT_CHARSET))));
requestHeaders.add("User-Agent", httpConfig.userAgent);
requestHeaders.add("Content-Type", httpConfig.contentType + ";charset=" + AnxinSignConst.DEFAULT_CHARSET);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(
paramMap, requestHeaders);
String response = restTemplate.postForObject(remoteUrl, paramMap, String.class);
return response;
} catch (Exception e) {
e.printStackTrace();
}
}
public void doPost(String remoteUrl, String data, String signature) {
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory)
.disableAutomaticRetries().build();
HttpPost post = new HttpPost(remoteUrl);
try {
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("data", data));
params.add(new BasicNameValuePair("signature", signature));
post.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
HttpResponse response = httpclient.execute(post);
System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
以上是 RestTemplate踩坑 的全部内容, 来源链接: utcz.com/z/515068.html