服务端
Nginx配置
server {
listen 8800 ssl;
server_name 127.0.0.1;
ssl_certificate /usr/local/NSP/nginx/certificate/nginx.crt;
ssl_certificate_key /usr/local/NSP/nginx/certificate/nginx.key;
ssl_password_file /usr/local/NSP/nginx/certificate/fifo;
ssl_protocols TLSv1.2;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES256-SHA:HIGH:!MEDIUM:!LOW:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4:@STRENGTH";
ssl_prefer_server_ciphers on;
ssl_dhparam ./ssl/dh2048.pem;
root /usr/local/NSP/nginx/html/deployui;
location /GatewayService/ {
proxy_pass https://10.31.31.112:18006/GatewayService/;
#如果证书里面配置了common name,且和proxy_pass中的ip不匹配,设置此参数为common name
proxy_ssl_name server.xncoding.com;
proxy_ssl_protocols TLSv1.1 TLSv1.2;
proxy_ssl_certificate /usr/local/NSP/nginx/certificate/nginx.crt;
proxy_ssl_certificate_key /usr/local/NSP/nginx/certificate/nginx.key;
proxy_ssl_password_file /usr/local/NSP/nginx/certificate/fifo;
proxy_ssl_trusted_certificate /usr/local/NSP/nginx/certificate/root.crt;
proxy_ssl_verify on;
proxy_ssl_verify_depth 2;
proxy_connect_timeout 600s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
default_type application/json;
proxy_intercept_errors off;
break;
}
}
Tomcat配置
注意下面的truststoreAlgorithm="SunX509" 是在tomcat8.5版本之后必须要添加的配置
SpringBoot配置
server:
ssl:
enabled: true
key-store: /cert/server.p12
key-store-password: 222222
key-store-type: PKCS12
trust-store: /cert/root.p12
trust-store-password: 333333
trust-store-type: PKCS12
client-auth: need
客户端
客户端RestTemplate
maven依赖
org.springframework.cloud
spring-cloud-commons
provided
com.sun.jersey.contribs
jersey-apache-client4
1.19.1
com.netflix.eureka
eureka-client
provided
org.springframework.boot
spring-boot-configuration-processor
provided
org.springframework.boot
spring-boot-autoconfigure
provided
org.springframework.boot
spring-boot-starter-web
provided
org.slf4j
slf4j-api
provided
HttpClientProperties类
@Component
@ConfigurationProperties(prefix = "dc.security.https.httpclient")
public class HttpClientProperties {
/**
* 是否开启服务端证书校验
*/
private boolean enabled = true;
/**
* 是否开启客户端证书发送
*/
private boolean clientCert = false;
/**
* 是否支持客户端负载均衡
*/
private boolean loadBalanced = true;
/**
* 是否支持eureka注册发现
*/
private boolean eureka = true;
/**
* CA根证书密钥库文件
*/
private String caRootCertKeyStore;
/**
* CA根证书密钥库密码
*/
private String caRootCertPassword;
/**
* 客户端证书库文件
*/
private String clientCertKeyStore;
/**
* 客户端证书库密码
*/
private String clientCertPassword;
/**
* 建立连接的超时时间
*/
private int connectTimeout = 20000;
/**
* 连接不够用的等待时间
*/
private int requestTimeout = 20000;
/**
* 每次请求等待返回的超时时间
*/
private int socketTimeout = 30000;
/**
* 每个主机最大连接数
*/
private int defaultMaxPerRoute = 100;
/**
* 最大连接数
*/
private int maxTotalConnections = 300;
/**
* 连接保持活跃的时间(Keep-Alive)
*/
private int defaultKeepAliveTimeMillis = 20000;
/**
* 空闲连接的生存时间
*/
private int closeIdleConnectionWaitTimeSecs = 30;
}
X509Util工具类
public class X509Util {
public static X509TrustManager getX509TrustManager(HttpClientProperties properties) {
try (FileInputStream rootKeyStore = new FileInputStream(properties.getCaRootCertKeyStore())){
// 加载服务端信任根证书库
KeyStore trustKeyStore = KeyStore.getInstance("PKCS12");
trustKeyStore.load(rootKeyStore, properties.getCaRootCertPassword().toCharArray());
// 初始化服务端信任证书管理器
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustKeyStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
return (X509TrustManager) trustManagers[0];
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
throw new RuntimeException("init trustManager error", e);
}
}
public static KeyManager[] getX509KeyManagers(HttpClientProperties properties) {
try (FileInputStream clientKeystore = new FileInputStream(properties.getClientCertKeyStore())) {
// 加载客户端证书库
KeyStore clientKeyStore = KeyStore.getInstance("PKCS12");
clientKeyStore.load(clientKeystore, properties.getClientCertPassword().toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, properties.getClientCertPassword().toCharArray());
return keyManagerFactory.getKeyManagers();
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException e) {
throw new RuntimeException("init keyManagers error", e);
}
}
}
ICrlService服务类
public interface ICrlService {
/**
* 实现此方法查询证书吊销列表
*
* @return 证书吊销序列号集合
*/
Set getCrlList();
}
AbstractUserInfoInterceptor
public abstract class AbstractUserInfoInterceptor implements HttpRequestInterceptor {
private static final Logger _logger = LoggerFactory.getLogger(AbstractUserInfoInterceptor.class);
/**
* 内部微服务之间调用增加的用户信息头
*/
private static final String HEADER_ACCESS_USER = "access-user";
public void process(HttpRequest request, HttpContext context) {
_logger.debug("ClientIPInterceptor start to handle...");
request.addHeader(HEADER_ACCESS_USER, loadUserInfo());
}
/**
* 加载用户信息
*
* @return 用户信息
*/
protected abstract String loadUserInfo();
}
AbstractClientIPInterceptor
public abstract class AbstractClientIPInterceptor implements HttpRequestInterceptor {
private static final Logger _logger = LoggerFactory.getLogger(AbstractClientIPInterceptor.class);
/**
* 内部微服务之间调用增加的IP地址头
*/
private static final String HEADER_X_REMOTE_USER_IP = "X-Remote-User-IP";
public void process(HttpRequest request, HttpContext context) {
_logger.debug("ClientIPInterceptor start to handle...");
request.addHeader(HEADER_X_REMOTE_USER_IP, loadRemoteClientIp());
}
/**
* 加载用户真实IP地址
*
* @return 用户真实IP地址
*/
protected abstract String loadRemoteClientIp();
}
SecurityEurekaClientConfig
@Configuration
@EnableConfigurationProperties({HttpClientProperties.class})
@ConditionalOnProperty(value = "dc.security.https.httpclient.eureka", havingValue = "true", matchIfMissing = true)
public class SecurityEurekaClientConfig {
private static final Logger logger = LoggerFactory.getLogger(SecurityEurekaClientConfig.class);
@Autowired
private HttpClientProperties properties;
@Bean
public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() throws Exception {
logger.info("DiscoveryClient.DiscoveryClientOptionalArgs init ...");
EurekaJerseyClientImpl.EurekaJerseyClientBuilder builder = new EurekaJerseyClientImpl.EurekaJerseyClientBuilder();
builder.withClientName("eureka-client");
builder.withCustomSSL(sslContextEureka());
builder.withMaxTotalConnections(10);
builder.withMaxConnectionsPerHost(10);
DiscoveryClient.DiscoveryClientOptionalArgs args = new DiscoveryClient.DiscoveryClientOptionalArgs();
args.setEurekaJerseyClient(builder.build());
return args;
}
private SSLContext sslContextEureka() throws Exception {
// 加载服务端信任Keystore
X509TrustManager origTrustmanager = getX509TrustManager(properties);
TrustManager[] wrappedTrustManagers = new TrustManager[]{
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
logger.info(">>>>>>>>>>>>>> sslContextEureka getAcceptedIssuers 00000000000000000 start ...");
return origTrustmanager.getAcceptedIssuers();
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
logger.info(">>>>>>>>>>>>>> sslContextEureka checkClientTrusted 111111111111 start ...");
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
logger.info(">>>>>>>>>>>>>> sslContextEureka checkServerTrusted 222222222222222 start ...");
}
}
};
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
if (properties.isClientCert()) { // 如果开启客户端证书校验,则需要发送客户端证书
sslContext.init(getX509KeyManagers(properties), wrappedTrustManagers, new java.security.SecureRandom());
} else { // 否则不需要发送客户端证书
sslContext.init(null, wrappedTrustManagers, new java.security.SecureRandom());
}
return sslContext;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException("eureka init sslContext error");
}
}
}
主配置类SecurityHttpClientConfig
@Configuration
@ConditionalOnProperty(value = "dc.security.https.httpclient.enabled", havingValue = "true")
@EnableScheduling
@EnableConfigurationProperties({HttpClientProperties.class})
public class SecurityHttpClientConfig {
private static final Logger logger = LoggerFactory.getLogger(SecurityHttpClientConfig.class);
@Autowired
private HttpClientProperties properties;
@Autowired
private ICrlService crlService;
@Autowired
protected ApplicationContext context;
@Bean
@ConditionalOnMissingBean(RestTemplate.class)
@ConditionalOnProperty(value = "dc.security.https.httpclient.load-balanced", havingValue = "false")
public RestTemplate restTemplateSimple(RestTemplateBuilder restTemplateBuilder) {
logger.info("simple RestTemplate");
return restTemplateBuilder.build();
}
@Bean
@LoadBalanced
@ConditionalOnMissingBean(RestTemplate.class)
@ConditionalOnProperty(value = "dc.security.https.httpclient.load-balanced", havingValue = "true", matchIfMissing = true)
public RestTemplate restTemplateLoadBalanced(RestTemplateBuilder restTemplateBuilder) {
logger.info("loadBalanced RestTemplate");
return restTemplateBuilder.build();
}
@Bean
@DependsOn(value = {"customRestTemplateCustomizer"})
public RestTemplateBuilder restTemplateBuilder(
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter, SSLContext sslContext) {
RestTemplateBuilder builder = new RestTemplateBuilder(customRestTemplateCustomizer(sslContext));
List> messageConverters = new ArrayList<>();
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
FormHttpMessageConverter formMessageConverter = new FormHttpMessageConverter();
messageConverters.add(stringHttpMessageConverter);
messageConverters.add(jackson2HttpMessageConverter);
messageConverters.add(formMessageConverter);
builder.messageConverters(messageConverters);
return builder;
}
@Bean
public RestTemplateCustomizer customRestTemplateCustomizer(SSLContext sslContext) {
return restTemplate -> {
HttpComponentsClientHttpRequestFactory rf = new HttpComponentsClientHttpRequestFactory();
rf.setHttpClient(httpClient(sslContext));
restTemplate.setRequestFactory(rf);
};
}
@Bean
public SSLContext sslContext() {
// 加载服务端信任Keystore
X509TrustManager origTrustmanager = getX509TrustManager(properties);
TrustManager[] wrappedTrustManagers = new TrustManager[]{
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
logger.info(">>>>>>>>>>>>>> getAcceptedIssuers 00000000000000000 start ...");
return origTrustmanager.getAcceptedIssuers();
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {
logger.info(">>>>>>>>>>>>>> checkClientTrusted 111111111111 start ...");
origTrustmanager.checkClientTrusted(certs, authType);
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {
logger.info(">>>>>>>>>>>>>> checkServerTrusted 222222222222222 start ...");
try {
//Original trust checking
origTrustmanager.checkServerTrusted(certs, authType);
//Check revocation with each cert
//from docs: CertificateException - if the certificate chain is not trusted by this TrustManager.
for (X509Certificate cert : certs) {
String certSerial = cert.getSerialNumber().toString(16).toUpperCase();
if (crlService.getCrlList().contains(certSerial)) {
logger.error("cert serial={} is in crl list, bad", certSerial);
throw new CertificateException();
}
}
} catch (CertificateExpiredException e) {
logger.error("cert expired error");
throw new CertificateExpiredException();
}
}
}
};
try {
SSLContext sslContext = SSLContext.getInstance("TLS");
if (properties.isClientCert()) { // 如果开启客户端证书校验,则需要发送客户端证书
sslContext.init(getX509KeyManagers(properties), wrappedTrustManagers, new java.security.SecureRandom());
} else { // 否则不需要发送客户端证书
sslContext.init(null, wrappedTrustManagers, new java.security.SecureRandom());
}
return sslContext;
} catch (NoSuchAlgorithmException | KeyManagementException e) {
throw new RuntimeException("init sslContext error");
}
}
@Bean
public PoolingHttpClientConnectionManager poolingConnectionManager(SSLContext sslContext) {
SSLConnectionSocketFactory sslsf;
try {
HostnameVerifier hostnameVerifier = (s, sslSession) -> {
try {
Certificate[] certs = sslSession.getPeerCertificates();
X509Certificate x509 = (X509Certificate) certs[0];
} catch (SSLPeerUnverifiedException e) {
logger.error("hostnameVerifier error", e);
return false;
}
return true;
};
sslsf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
} catch (Exception e) {
logger.error("Pooling Connection Manager Initialisation failure");
throw new RuntimeException("Pooling Connection Manager Initialisation failure", e);
}
Registry socketFactoryRegistry = RegistryBuilder
.create()
.register("https", sslsf)
.register("http", new PlainConnectionSocketFactory())
.build();
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(
socketFactoryRegistry);
poolingConnectionManager.setMaxTotal(properties.getMaxTotalConnections()); //最大连接数
poolingConnectionManager.setDefaultMaxPerRoute(properties.getDefaultMaxPerRoute()); //同路由并发数
return poolingConnectionManager;
}
@Bean
public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
return (response, httpContext) -> {
HeaderElementIterator it = new BasicHeaderElementIterator
(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000;
}
}
return properties.getDefaultKeepAliveTimeMillis();
};
}
private CloseableHttpClient httpClient(SSLContext sslContext) {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(properties.getRequestTimeout())
.setConnectTimeout(properties.getConnectTimeout())
.setSocketTimeout(properties.getSocketTimeout()).build();
HttpClientBuilder httpClientBuilder = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(poolingConnectionManager(sslContext))
.setKeepAliveStrategy(connectionKeepAliveStrategy())
.setRetryHandler(new DefaultHttpRequestRetryHandler(3, true));
// 增加请求拦截器
Map interceptorMap = context.getBeansOfType(HttpRequestInterceptor.class);
if (interceptorMap.size() > 0) {
for (HttpRequestInterceptor interceptor : interceptorMap.values()) {
httpClientBuilder.addInterceptorLast(interceptor);
}
}
return httpClientBuilder.build();
}
/**
* You can't set an idle connection timeout in the config for Apache HTTP Client. The reason is that there is a
* performance overhead in doing so.
*
* The documentation clearly states why, and gives an example of an idle connection monitor implementation you can
* copy. Essentially this is another thread that you run to periodically call closeIdleConnections on
* HttpClientConnectionManager
*
* http://hc.apache.org/httpcomponents-client-ga/tutorial/html/connmgmt.html
*
* @return 线程
*/
@Bean
public Runnable idleConnectionMonitor() {
return new Runnable() {
@Override
@Scheduled(fixedDelay = 10000)
public void run() {
try {
PoolingHttpClientConnectionManager connectionManager = poolingConnectionManager(sslContext());
logger.trace("run IdleConnectionMonitor - Closing expired and idle connections...");
connectionManager.closeExpiredConnections();
connectionManager
.closeIdleConnections(properties.getCloseIdleConnectionWaitTimeSecs(), TimeUnit.SECONDS);
} catch (Exception e) {
logger.error("run IdleConnectionMonitor - Exception occurred. msg={}, e={}", e.getMessage(), e);
}
}
};
}
@Bean
@ConditionalOnMissingBean(TaskScheduler.class)
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("poolScheduler");
scheduler.setPoolSize(10);
return scheduler;
}
@Bean
@ConditionalOnMissingBean(ICrlService.class)
public ICrlService crlService() {
return HashSet::new;
}
}
客户端配置
dc:
security:
https:
httpclient:
enabled: true
ca-root-cert-key-store: /cert/root.p12 #根证书库
ca-root-cert-password: 333333 #根证书库密码
client-cert: true #开启客户端证书
client-cert-key-store: /cert/server.p12 #客户端证书库
client-cert-password: 222222 #客户端证书库密码