• 103749

    文章

  • 803

    评论

  • 12

    友链

  • 最近新加了换肤功能,大家多来逛逛吧~~~~
  • 喜欢这个网站的朋友可以加一下QQ群,我们一起交流技术。

RestTemplate踩坑

撸了今年阿里、腾讯和美团的面试,我有一个重要发现.......>>

池化 + SSL 的 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();
	}
}

695856371Web网页设计师②群 | 喜欢本站的朋友可以收藏本站,或者加入我们大家一起来交流技术!

0条评论

Loading...


自定义皮肤 主体内容背景
打开支付宝扫码付款购买视频教程
遇到问题联系客服QQ:419400980
注册梁钟霖个人博客