创建根证书
openssl genrsa -out root.key 2048
openssl req -utf8 -new -out root.csr -key root.key
openssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 36500
创建(java的信任库)
keytool -import -noprompt -trustcacerts -alias root -file root.crt -keystore trust-keys.p12 -storetype PKCS12 -storepass ‘12345678’
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:上海市
Locality Name (eg, city) []:徐汇区
Organization Name (eg, company) [Internet Widgits Pty Ltd]:信昊
Organizational Unit Name (eg, section) []:信昊
Common Name (e.g. server FQDN or YOUR name) []:root
Email Address []:
创建服务端证书
openssl genrsa -out server.key 2048
openssl req -utf8 -new -out server.csr -key server.key
openssl x509 -req -in server.csr -out server.crt -signkey server.key -CA root.crt -CAkey root.key -CAcreateserial -days 36500
服务端p12格式证书(server.key+server.crt+root.crt -> server.p12)
openssl pkcs12 -export -inkey server.key -passin pass:‘12345678’ -in server.crt -chain -CAfile root.crt -out server.p12 -password pass:‘12345678’
导出server的公钥
keytool -storetype PKCS12 -keystore server.p12 -export -alias 1 -file server-public-key.cer
加入信任库
keytool -storetype PKCS12 -import -alias server -v -file server-public-key.cer -keystore trust-keys.p12
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:上海市
Locality Name (eg, city) []:徐汇区
Organization Name (eg, company) [Internet Widgits Pty Ltd]:信昊
Organizational Unit Name (eg, section) []:信昊
Common Name (e.g. server FQDN or YOUR name) []:server
Email Address []:
创建客户端证书
openssl genrsa -out client.key 2048
openssl req -utf8 -new -out client.csr -key client.key
openssl x509 -req -in client.csr -out client.crt -signkey client.key -CA root.crt -CAkey root.key -CAcreateserial -days 36500
生客户端p12格式证书(client.key+client.crt+rootca.crt -> client.p12)
openssl pkcs12 -export -inkey client.key -passin pass:‘12345678’ -in client.crt -chain -CAfile root.crt -out client.p12 -password pass:‘12345678’
导出client的公钥
keytool -storetype PKCS12 -keystore server.p12 -export -alias 1 -file client-public-key.cer
加入信任库
keytool -storetype PKCS12 -import -alias client -v -file client-public-key.cer -keystore trust-keys.p12
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:上海市
Locality Name (eg, city) []:徐汇区
Organization Name (eg, company) [Internet Widgits Pty Ltd]:信昊
Organizational Unit Name (eg, section) []:信昊
Common Name (e.g. server FQDN or YOUR name) []:client
Email Address []:
server配置
ssl:
enabled: true
client-auth: need
key-store: classpath:ssl/server.p12
key-store-password: 12345678
key-store-type: PKCS12
trust-store: classpath:ssl/trust-keys.p12
trust-store-password: 12345678
trust-store-type: PKCS12
server RestTemplate代码
package com.xinhaosoft.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.time.Duration;
/**
* SSL RestTemplate配置
*
* @author CleanCode
*/
@Configuration
@Slf4j
public class RestTemplateConfig {
@Value("${server.ssl.key-store-type}")
private String clientKeyType;
@Value("${server.ssl.key-store}")
private String clientPath;
@Value("${server.ssl.key-store-password}")
private String clientPass;
@Value("${server.ssl.trust-store-type}")
private String trustKeyType;
@Value("${server.ssl.trust-store}")
private String trustPath;
@Value("${server.ssl.trust-store-password}")
private String trustPass;
private final ResourceLoader resourceLoader;
public RestTemplateConfig(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Bean("sslRestTemplate")
public RestTemplate restTemplate() {
RestTemplate restTemplate = null;
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
// 客户端证书类型
KeyStore clientStore = KeyStore.getInstance(clientKeyType);
// 加载客户端证书,即自己的私钥
InputStream keyStream = resourceLoader.getResource(clientPath).getInputStream();
clientStore.load(keyStream, clientPass.toCharArray());
// 创建密钥管理工厂实例
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 初始化客户端密钥库
keyManagerFactory.init(clientStore, clientPass.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
// 创建信任库管理工厂实例
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore trustStore = KeyStore.getInstance(trustKeyType);
InputStream trustStream = resourceLoader.getResource(trustPath).getInputStream();
trustStore.load(trustStream, trustPass.toCharArray());
// 初始化信任库
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
// 建立TLS连接
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化SSLContext
sslContext.init(keyManagers, trustManagers, new SecureRandom());
// INSTANCE 忽略域名检查
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpclient = HttpClients
.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
requestFactory.setHttpClient(httpclient);
requestFactory.setConnectTimeout((int) Duration.ofSeconds(15).toMillis());
restTemplate = new RestTemplate(requestFactory);
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException |
UnrecoverableKeyException | IOException e) {
e.printStackTrace();
}
return restTemplate;
}
}
服务端测试代码
package com.xinhaosoft.controller;
import com.xinhaosoft.vo.DbScanTableVO;
import com.xinhaosoft.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 双向认证测试Controller
*
* @author CleanCode
*/
@RestController
@Slf4j
public class TestController {
private final RestTemplate restTemplate;
public TestController(@Qualifier(value = "sslRestTemplate") RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* 远程调用测试
*/
@GetMapping("/public/ssl/test")
public String sslTest() {
ResponseEntity<Result<DbScanTableVO>> exchange = restTemplate.exchange("https://192.168.100.15:8005/dam-client/scan/table",
HttpMethod.GET,
null,
new ParameterizedTypeReference<Result<DbScanTableVO>>() {
});
log.info("forObject:{}", exchange.getBody());
return "双向认证调用通过";
}
/**
* 被远程调用的方法
*/
@GetMapping("/public/scan/table")
public Result<DbScanTableVO> getScanTable() {
DbScanTableVO dbScanTableVO = new DbScanTableVO();
dbScanTableVO.setId(1L).setName("test");
return Result.buildOk("成功", dbScanTableVO);
}
}
client配置
server:
ssl:
enabled: true
client-auth: need
key-store: classpath:ssl/client.p12
key-store-password: 12345678
key-store-type: PKCS12
trust-store: classpath:ssl/trust-keys.p12
trust-store-password: 12345678
trust-store-type: PKCS12
client RestTemplate代码
package com.xinhaosoft.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.*;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.CertificateException;
import java.time.Duration;
/**
* SSL RestTemplate配置
*
* @author CleanCode
*/
@Configuration
@Slf4j
public class RestTemplateConfig {
@Value("${ssl.key-store-type}")
private String clientKeyType;
@Value("${ssl.key-store}")
private String clientPath;
@Value("${ssl.key-store-password}")
private String clientPass;
@Value("${ssl.trust-store-type}")
private String trustKeyType;
@Value("${ssl.trust-store}")
private String trustPath;
@Value("${ssl.trust-store-password}")
private String trustPass;
private final ResourceLoader resourceLoader;
public RestTemplateConfig(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Bean("sslRestTemplate")
public RestTemplate restTemplate() {
RestTemplate restTemplate = null;
try {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
// 客户端证书类型
KeyStore clientStore = KeyStore.getInstance(clientKeyType);
// 加载客户端证书,即自己的私钥
InputStream keyStream = resourceLoader.getResource(clientPath).getInputStream();
clientStore.load(keyStream, clientPass.toCharArray());
// 创建密钥管理工厂实例
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
// 初始化客户端密钥库
keyManagerFactory.init(clientStore, clientPass.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
// 创建信任库管理工厂实例
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore trustStore = KeyStore.getInstance(trustKeyType);
InputStream trustStream = resourceLoader.getResource(trustPath).getInputStream();
trustStore.load(trustStream, trustPass.toCharArray());
// 初始化信任库
trustManagerFactory.init(trustStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
// 建立TLS连接
SSLContext sslContext = SSLContext.getInstance("TLS");
// 初始化SSLContext
sslContext.init(keyManagers, trustManagers, new SecureRandom());
// INSTANCE 忽略域名检查
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpclient = HttpClients
.custom()
.setSSLSocketFactory(sslConnectionSocketFactory)
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
requestFactory.setHttpClient(httpclient);
requestFactory.setConnectTimeout((int) Duration.ofSeconds(15).toMillis());
restTemplate = new RestTemplate(requestFactory);
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException | CertificateException |
UnrecoverableKeyException | IOException e) {
e.printStackTrace();
}
return restTemplate;
}
}
客户端测试代码
package com.xinhaosoft.controller;
import com.xinhaosoft.vo.DbScanTableVO;
import com.xinhaosoft.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 双向认证测试Controller
*
* @author CleanCode
*/
@RestController
@Slf4j
public class TestController {
private final RestTemplate restTemplate;
public TestController(@Qualifier(value = "sslRestTemplate") RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* 被远程调用的方法
*
* @return Result
*/
@GetMapping("/scan/table")
public Result<DbScanTableVO> getScanTable() {
DbScanTableVO dbScanTableVO = new DbScanTableVO();
dbScanTableVO.setId(1L).setName("test");
return Result.buildOk("成功", dbScanTableVO);
}
/**
* 远程调用测试
*
* @return Result
*/
@GetMapping("/public/ssl/test")
public String sslTest() {
//通过nginx做了代理所以端口是443
ResponseEntity<Result<DbScanTableVO>> exchange = restTemplate.exchange("https://192.168.100.15/dam/public/scan/table",
HttpMethod.GET,
null,
new ParameterizedTypeReference<Result<DbScanTableVO>>() {
});
log.info("forObject:{}", exchange.getBody());
return "双向认证调用通过";
}
}
nginx配置
# HTTPS server
#
server {
listen 443 ssl;
server_name 192.168.100.15;
# 服务端证书和私钥(pem格式)
ssl_certificate D:/server/nginx-1.26.0/ssl/server.crt;
ssl_certificate_key D:/server/nginx-1.26.0/ssl/server.key;
# 根证书(pem格式)
ssl_client_certificate D:/server/nginx-1.26.0/ssl/root.crt;
# 开启双向认证
ssl_verify_client on;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
# 代理后端服务
proxy_pass http://192.168.100.15:8004;
#root html;
#index index.html index.htm;
}
}