前面文章有讲过 “spring boot 使用RestTemplate信任所有https请求”,但实际生产使用时毕竟违反规范使用,造成安全问题,
这里就分享一下通过服务方提供的认证证书来实现https请求;
一、证书转换
1、服务方给的证书多为"cer"类型,比如直接从浏览器中下载下来的,该类证书不能直接使用java调用认证,需转换为java可识别的类型,比如".keystore";
2、利用jdk中"keytool"命令进行转换,即证书导入,windows系统在证书目录打开cmd,执行命令:
keytool -importcert -keystore client_trust.keystore -file D:\cer\server.cer -alias client_trust_server -storepass 111111 -noprompt
注意:切记“-storepass”后输入的密码,后面要用
参数说明
- importcert 表示导入信任证书
- file 指示导入证书,支持pem/der格式
- keystore 指示目标keystore文件
- storepass 指示新的keystore密钥
- alias 指示trust证书在keystore中的别名
- noprompt 指示不弹出提示
导入后的证书为 trustedCertEntry 实体类型,而私钥证书为 PrivateKeyEntry
二、Spring Boot项目中导入证书
在“resources”目录下新建文件夹“keystore”,并将转换后的证书导入进来:
三、 主要依赖
<!-- SpringBoot版本-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/>
</parent>
<!-- 主要依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
四、配置RestTemplate实例
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
/**
* RestTemplate - https证书认证
*/
@Configuration
public class RestTemplateOfCerConfig {
/**
* resources 目录下证书路径
* cerPath = "classpath:/keystore/client_trust.keystore";
*/
@Value("${cer-path}")
private String cerPath;
/**
* 使用"keytool"命令导入证书时输入的密码
* cerPwd = "111111";
*/
@Value("${cer-pwd}")
private String cerPwd;
@Bean(name = "cerRestTemplate")
public RestTemplate hwRestTemplate(HttpComponentsClientHttpRequestFactory cerHttpsFactory) {
RestTemplate restTemplate = new RestTemplate(cerHttpsFactory);
restTemplate.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse clientHttpResponse) {
return false;
}
@Override
public void handleError(ClientHttpResponse clientHttpResponse) {
//默认处理非200的返回,会抛异常
}
});
return restTemplate;
}
@Bean(name = "cerHttpsFactory")
public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() throws Exception {
CloseableHttpClient httpClient = createCloseableHttpClient();
HttpComponentsClientHttpRequestFactory httpsFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
httpsFactory.setReadTimeout(2000);
httpsFactory.setConnectTimeout(2000);
return httpsFactory;
}
/**
* https协议证书认证
*
* @return
* @throws Exception
*/
private CloseableHttpClient createCloseableHttpClient() throws Exception {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(resourceLoader(cerPath), cerPwd.toCharArray());
SSLContext sslContext = SSLContexts.custom()
.loadTrustMaterial(keyStore, new TrustSelfSignedStrategy()).build();
// 这里的通信协议要根据使用的JDK版本来适配
SSLConnectionSocketFactory sslfactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE);
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
CloseableHttpClient closeableHttpClient = httpClientBuilder.setSSLSocketFactory(sslfactory).build();
return closeableHttpClient;
}
/**
* 读取文件信息
*
* @param fileFullPath
* @return
* @throws IOException
*/
public InputStream resourceLoader(String fileFullPath) throws IOException {
ResourceLoader resourceLoader = new DefaultResourceLoader();
return resourceLoader.getResource(fileFullPath).getInputStream();
}
}
五、JDK版本与通信协议对照
如果你的JDK版本是1.8的,通过上面的配置已可以实现https的SSL请求,但如果你的JDK版本低于1.8,可能就要报错了,
原因是JDK版本与通信协议不一致,具体的版本对照如下图:
找到对应的协议后只需修改以下配置即可:
六、RestTemplate Bean注入
使用时将以上“RestTemplate ”实例注入,此时可实现指定"https"地址的请求,
与通常我们使用“RestTemplate ”调用各种类型的请求方法一样,
“RestTemplate ”的通用工具类可参照(文章最后一段): spring boot 使用RestTemplate信任所有https请求