mysql 证书双向认证_证书双向认证 - 一刀的个人空间 - OSCHINA - 中文开源技术交流社区...

本文详细介绍了如何配置MySQL的证书双向认证,包括Nginx、Tomcat和SpringBoot的配置步骤,并提供了相关代码示例。同时,还讲解了客户端如何进行证书校验和加载,确保安全的数据传输。
摘要由CSDN通过智能技术生成

服务端

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版本之后必须要添加的配置

acceptCount="100" maxHttpHeaderSize="8192" maxKeepAliveRequests="100"

scheme="https" secure="true"

keystoreFile="conf/server.p12"

keystorePass="222222"

keystoreType="PKCS12"

clientAuth="true"

truststoreFile="conf/root.p12"

truststorePass="333333"

truststoreType="PKCS12"

truststoreAlgorithm="SunX509"

ciphers="TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"

maxPostSize="10240" connectionTimeout="20000"

allowTrace="false" xpoweredBy="false"

server="WebServer"

URIEncoding="UTF-8" sslEnabledProtocols="TLSv1.2,TLSv1.1"/>

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 {

private static final Logger logger = LoggerFactory.getLogger(X509Util.class);

public static SSLContext initSslContext(HttpClientProperties properties) {

// 加载服务端信任Keystore

X509TrustManager origTrustmanager = getX509TrustManager(properties);

TrustManager[] wrappedTrustManagers = new TrustManager[]{

new X509TrustManager() {

@Override

public X509Certificate[] getAcceptedIssuers() {

logger.debug(">>>>>>>>>>>>>> getAcceptedIssuers 00000000000000000 start ...");

return origTrustmanager.getAcceptedIssuers();

}

@Override

public void checkClientTrusted(X509Certificate[] certs, String authType) throws CertificateException {

logger.debug(">>>>>>>>>>>>>> checkClientTrusted 111111111111 start ...");

origTrustmanager.checkClientTrusted(certs, authType);

}

@Override

public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {

logger.debug(">>>>>>>>>>>>>> checkServerTrusted 222222222222222 start ...");

// Original trust checking

origTrustmanager.checkServerTrusted(certs, authType);

if (certs.length > 1) {

for (int i = 1; i < certs.length; i++) {

X509Certificate ca = certs[i];

// 根证书的扩展属性中“基本限制”属性是否包括“CA”

if (ca.getBasicConstraints() == -1) {

logger.error("check CA certificate error");

throw new CertificateException("not a CA certificate");

}

// 根证书的扩展属性中“密钥用法”中是否包括“证书签名”属性

/*

* KeyUsage ::= BIT STRING {

* digitalSignature (0),

* nonRepudiation (1),

* keyEncipherment (2),

* dataEncipherment (3),

* keyAgreement (4),

* keyCertSign (5),

* cRLSign (6),

* encipherOnly (7),

* decipherOnly (8) }

*/

boolean[] keyUsages = ca.getKeyUsage();

if (!keyUsages[5]) {

logger.error("check keyusage keyCertSign error");

throw new CertificateException("keyusage has not value keyCertSign");

}

}

}

}

}

};

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");

}

}

public static X509TrustManager getX509TrustManager(HttpClientProperties properties) {

String caRootCertKeyStore = properties.getCaRootCertKeyStore();

try {

PathUtils.checkRegularAndSecure(caRootCertKeyStore);

} catch (SecurityException e) {

throw new RuntimeException("init trustManager, check regular and secure error", e);

}

try (FileInputStream rootKeyStore = new FileInputStream(caRootCertKeyStore)) {

// 加载服务端信任根证书库

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);

}

}

private static KeyManager[] getX509KeyManagers(HttpClientProperties properties) {

String clientCertKeyStore = properties.getClientCertKeyStore();

try {

PathUtils.checkRegularAndSecure(clientCertKeyStore);

} catch (SecurityException e) {

throw new RuntimeException("init KeyManager, check regular and secure error", e);

}

try (FileInputStream clientKeystore = new FileInputStream(clientCertKeyStore)) {

// 加载客户端证书库

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() {

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() {

logger.info("SecurityEurekaClientConfig init sslContext...");

return X509Util.initSslContext(properties);

}

}

主配置类SecurityHttpClientConfig

/**

* 支持HTTPS协议访问和证书双向认证的HttpClient配置类

*

* @author x00482439

* @version v1.0, 2019-05-14

* @since Security SDK

*/

@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

protected ApplicationContext context;

@Bean(name = "simpleRestTemplate")

@ConditionalOnMissingBean(name = "simpleRestTemplate")

@ConditionalOnProperty(value = "dc.security.https.httpclient.simple", havingValue = "true")

public RestTemplate simpleRestTemplate(RestTemplateBuilder restTemplateBuilder) {

logger.info("simple RestTemplate initialize");

return restTemplateBuilder.build();

}

@Bean(name = "restTemplate")

@ConditionalOnMissingBean(name = "restTemplate")

@ConditionalOnProperty(value = "dc.security.https.httpclient.load-balanced", havingValue = "true", matchIfMissing = true)

@LoadBalanced

public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {

logger.info("loadBalanced RestTemplate initialize");

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() {

logger.info("SecurityHttpClientConfig init sslContext...");

return X509Util.initSslContext(properties);

}

@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))

.setConnectionManagerShared(true)

.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.", 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 #客户端证书库密码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值