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 error

res: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

回到顶部