Spring Boot SSL 客户端
Posted
技术标签:
【中文标题】Spring Boot SSL 客户端【英文标题】:Spring Boot SSL Client 【发布时间】:2015-08-26 12:40:36 【问题描述】:我是 Spring Boot 的新手。到目前为止,我很享受。我开发了一个演示 SSL REST Web 服务器,可以正确处理相互 X.509 证书身份验证。使用带有自签名客户端和服务器证书的 IE 浏览器,我测试了演示 rest web 服务器工作正常——服务器和浏览器都成功地交换和验证了彼此的证书。
我在查找 SSL 客户端示例时遇到了问题,该示例显示了如何包含客户端证书并颁发 https。有人有一个简单的 REST 客户端示例来展示如何使用我的 ssl 服务器吗?
最好的问候, 史蒂夫·曼斯菲尔德
【问题讨论】:
您心目中的客户是什么? Java(使用 Spring)?还是有什么不同? Spring 最好,但 Java 也可以。 嗨,史蒂夫,我偶然发现了这个问题,想知道为什么需要包含 SSL 客户端代码? 可能无法回答你的问题,但我开始使用 OkHttp 客户端,它就像标准 java 一样工作 【参考方案1】:我知道为时已晚,但这是适合我的代码。
@SpringBootApplication
public class Application
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String args[])
makeWebServiceCall();
public static void makeWebServiceCall()
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext;
ResponseEntity<String> response = null;
try
sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
StringBuffer plainCreds = new StringBuffer();
plainCreds.append("username");
plainCreds.append(":");
plainCreds.append("password");
byte[] plainCredsBytes = plainCreds.toString().getBytes();
byte[] base64CredsBytes = Base64.getEncoder().encode(plainCredsBytes);
String userBase64Credentials = new String(base64CredsBytes);
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Basic " + userBase64Credentials);
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity<>(headers);
String url = "https:restUrl";
response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
if(response.getStatusCodeValue() == 200)
log.info("Success! Further processing based on the need");
else
log.info("****************Status code received: " + response.getStatusCodeValue() + ".************************");
catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e)
log.error("Exception occured. Here are the exception details: ", e);
catch(HttpClientErrorException e)
if(e.getRawStatusCode() == 403)
log.info("****************Status code received: " + e.getRawStatusCode() + ". You do not have access to the requested resource.************************");
else if(e.getRawStatusCode() == 404)
log.info("****************Status code received: " + e.getRawStatusCode() + ". Resource does not exist(or) the service is not up.************************");
else if(e.getRawStatusCode() == 400)
log.info("****************Status code received: " + e.getRawStatusCode() + ". Bad Request.************************");
else
log.info("****************Status code received: " + e.getRawStatusCode() + ".************************");
log.info("****************Response body: " + e.getResponseBodyAsString() + "************************");
这里是 maven 文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-consuming-rest</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
【讨论】:
【参考方案2】:其他方式来做到这一点。注入 keyStoreLocation 和 keyStorePassword 的值
@Configuration
public class SampleSSLClient extends RestTemplate
/** The key store password. */
private String keyStorePassword;
/** The key store location. */
private String keyStoreLocation;
/** The rest template. */
@Autowired
private RestTemplate restTemplate;
/** The http components client http request factory. */
private HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory;
/**
* Instantiates a new custom rest template.
*/
public CustomRestTemplate()
super();
public CustomRestTemplate(RestTemplate restTemplate)
this.restTemplate = getRestTemplate();
/**
* Rest template.
*
* @return the rest template
*/
public RestTemplate getRestTemplate()
if (null == httpComponentsClientHttpRequestFactory)
httpComponentsClientHttpRequestFactory = loadCert();
restTemplate.setRequestFactory(httpComponentsClientHttpRequestFactory);
return restTemplate;
/**
* Load cert.
*
* @return the http components client http request factory
*/
private HttpComponentsClientHttpRequestFactory loadCert()
try
char[] keypass = keyStorePassword.toCharArray();
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(getkeyStore(keyStoreLocation, keypass), keypass)
.loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
HttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
httpComponentsClientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
httpComponentsClientHttpRequestFactory.setConnectTimeout(5000);
httpComponentsClientHttpRequestFactory.setReadTimeout(30000);
catch (Exception ex)
LOGGER.error(MessageFormat.format("Some Error", ex.getMessage()), ex);
return httpComponentsClientHttpRequestFactory;
/**
* Key store.
*
* @param storePath the store path
* @param password the password
* @return the key store
*/
private KeyStore getkeyStore(String storePath, char[] password)
KeyStore keyStore;
try
keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
File key = ResourceUtils.getFile(storePath);
try (InputStream in = new FileInputStream(key))
keyStore.load(in, password);
catch (Exception ex)
LOGGER.error(MessageFormat.format("Some Error", ex.getMessage()), ex);
return keyStore;
/**
* Sets the key store password.
*
* @param keyStorePassword the new key store password
*/
public void setKeyStorePassword(String keyStorePassword)
this.keyStorePassword = keyStorePassword;
/**
* Sets the key store location.
*
* @param keyStoreLocation the new key store location
*/
public void setKeyStoreLocation(String keyStoreLocation)
this.keyStoreLocation = keyStoreLocation;
/**
* Sets the rest template.
*
* @param restTemplate the new rest template
*/
public void setRestTemplate(RestTemplate restTemplate)
this.restTemplate = restTemplate;
【讨论】:
吞下异常并不是最好的办法。 这就是所谓的“处理期望”@jannis【参考方案3】:user1707141 的示例对我不起作用,skmansfield 似乎取决于特定文件,这些文件与 Spring Boot / Maven 不约定。 Andy Wilkinson 的回答也使用了构造函数 SSLConnectionSocketFactory,它在 Apache httpclient 4.4+ 中已被弃用,而且看起来也很复杂。
所以我创建了一个示例项目,应该在此处显示所有内容100% 可理解:https://github.com/jonashackt/spring-boot-rest-clientcertificate
除了在您的测试类中正常使用带有@Autowired
的 RestTemplate 之外,请务必像这样配置您的 RestTemplate:
package de.jonashackt.restexamples;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.ResourceUtils;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
@Configuration
public class RestClientCertTestConfiguration
private String allPassword = "allpassword";
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception
SSLContext sslContext = SSLContextBuilder
.create()
.loadKeyMaterial(ResourceUtils.getFile("classpath:keystore.jks"), allPassword.toCharArray(), allPassword.toCharArray())
.loadTrustMaterial(ResourceUtils.getFile("classpath:truststore.jks"), allPassword.toCharArray())
.build();
HttpClient client = HttpClients.custom()
.setSSLContext(sslContext)
.build();
return builder
.requestFactory(() -> new HttpComponentsClientHttpRequestFactory(client))
.build();
【讨论】:
无法自动装配,找不到“RestTemplateBuilder”类型的bean【参考方案4】:这对我有用:
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
javax.net.ssl.SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy).build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
【讨论】:
【参考方案5】:我无法让 Andy 提交的上述客户端工作。我不断收到错误消息,说“localhost!= clientname”。无论如何,我让它正常工作。
import java.io.IOException;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.URI;
import org.apache.commons.httpclient.methods.GetMethod;
public class SSLClient
static
System.setProperty("javax.net.ssl.trustStore","c:/apachekeys/client1.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.keyStore", "c:/apachekeys/client1.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
public static void main(String[] args) throws HttpException, IOException
HttpClient client = new HttpClient();
GetMethod method = new GetMethod();
method.setURI(new URI("https://localhost:8443/restserver", false));
client.executeMethod(method);
System.out.println(method.getResponseBodyAsString());
【讨论】:
这是系统范围的,它甚至适用于jdbc连接,不推荐 这在您的服务器需要使用数字证书进行客户端身份验证时很有用。【参考方案6】:鉴于您使用的是 Spring,下面的示例展示了如何使用配置了客户端证书的 Spring 的 RestTemplate
和 Apache 的 HttpClient
并信任来自服务器的自签名证书:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File("keystore.jks")),
"secret".toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, "password".toCharArray()).build());
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
ResponseEntity<String> response = restTemplate.getForEntity(
"https://localhost:8443", String.class);
【讨论】:
编译器对 SSLConnectionSocketFactory 大吼大叫。我玩了几个小时,但没有运气。可以看看修一下吗? 关于代码的另一个问题。您在密钥库中有“秘密”,然后在 loadkeymaterial 中有“密码”。什么是秘密? 我认为SSLConnectionSocketFactory
没有什么可修复的。这是它的javadoc,如果有帮助的话。 secret
是整个密钥库的密码。 password
是密钥库中密钥的密码。
谢谢安迪...我找到了问题,我正在解决它。由于某种原因,spring boot 没有下载必要的 jars。现在正在努力寻找他们。明天第一件事应该有工作。
嗨,安迪,你知道我在哪里可以找到 SSLConnectionSocketFactory 的 jar 吗?以上是关于Spring Boot SSL 客户端的主要内容,如果未能解决你的问题,请参考以下文章
Spring Boot文档阅读笔记-how-to-implement-2-way-ssl-using-spring-boot
如何配置 Spring Boot 应用程序以接受未知的 SSL 客户端证书?
spring boot 添加整合ssl使得http变成https方法
如何在 Spring Boot 嵌入式 tomcat 中设置 HTTPS SSL 密码套件首选项