一、前提条件
·安装1.8或以上版本 JDK
·下载 TSF Demo 工程(springboot 版本2.0+,tomcat 版本8.5+)
二、步骤1:制作 SSL 证书
通过 JDK 自带的 keytool 执行以下命令:
keytool -genkey -alias tomcat -keyalg RSA -keystore ./keystore.jks -storepass 123456
执行成功后当前目录下会生成 keystore.jks 证书文件。
三、步骤2:改造 provider-demo 工程
1. 复制 jks 证书文件到 provider-demo 工程的 resources 目录下。
2. 修改 spring 配置文件,在 bootstrap.yaml 文件中增加如下配置。
server.http2.enabled=trueserver.ssl.key-store=classpath:keystore.jksserver.ssl.key-store-password: 123456
3. 启动 provider-demo,浏览器访问 https://127.0.0.1:18081/echo/1。
Chrome 可能会提示“你的连接不是私密连接”。
只需单击任意空白处,键入“thisisunsafe”即可。
打开 Console,Protocol 的值为 h2 表示配置成功。
参见资料:
·Spring 配置 http2 文档:howto-configure-http2
·Tomcat 版本差异文档:whichversion
4. 开启 http 访问端口(可选)。
由于此时只能通过 https 方式访问,可加入 Tomcat 配置开放新端口来支持 http 访问。
4.1 在启动类中增加 Bean。
package com.tsf.demo.provider; import org.apache.catalina.connector.Connector; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.tsf.annotation.EnableTsf; @SpringBootApplication @EnableFeignClients // 使用Feign微服务调用时请启用 @EnableTsf public class ProviderApplication { @Value("${http.port}") private Integer port; /** * Tomcat增加支持http访问 **/ @Bean public ServletWebServerFactory servletContainer() { TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); // connector.setSecure(false); // connector.setScheme("http"); connector.setPort(port); tomcat.addAdditionalTomcatConnectors(connector); return tomcat; } public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); } }
5. 在 Bootstrap.yml 配置文件中增加自定义配置。
http.port=18082
6. 重启 provider-demo,浏览器访问 http://127.0.0.1:18082/echo/1。
此时 provider-demo 完成支持 https 和 http(通过不同端口访问),其中 https 访问时使用 http2 协议。
参见资料:Spring 配置 Tomcat 代码示例:SampleTomcatTwoConnectorsApplication.java。
四、步骤3:改造 consumer-demo 工程
1. proxy 中的 @FeignClient 注解需指定 https 方式访问。
@FeignClient(name = "https://provider-demo")
2. RestTemplate 同理。
restTemplate.getForObject("https://provider-demo/echo/" + str, String.class);
3. 通过注入不同的 bean 选择是否使用SSL证书认证(以下步骤二选一):
使用 SSL 证书认证访问方式忽略 SSL 认证方式1. 复制 jks 证书文件到 consumer-demo 工程的 resources 目录。2. spring 配置文件中增加自定义配置。
test-ssl-config.key-store=classpath:keystore.jkstest-ssl-config.key-store-password: 1234563. 修改 bean(restTemplate、feignClient)
package com.tsf.demo.consumer; import feign.Client; 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.HttpClients; import org.apache.http.ssl.SSLContexts; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.ribbon.SpringClientFactory; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory; import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient; import org.springframework.context.annotation.Bean; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.tsf.annotation.EnableTsf; import org.springframework.util.ResourceUtils; import org.springframework.web.client.AsyncRestTemplate; import org.springframework.web.client.RestTemplate; import javax.net.ssl.*; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.security.*; import java.security.cert.CertificateException; @SpringBootApplication @EnableFeignClients // 使用Feign微服务调用时请启用 @EnableTsf public class ConsumerApplication { @Value("${test-ssl-config.key-store}") private String file; @Value("${test-ssl-config.key-store-password}") private String password; @LoadBalanced @Bean public RestTemplate restTemplate() { SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(getSSLSocket(file, password), new String[]{"TLSv1"}, null, NoopHostnameVerifier.INSTANCE); CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(csf) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient); return new RestTemplate(requestFactory); } @Bean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { return new LoadBalancerFeignClient( new Client.Default(getSSLSocket(file, password).getSocketFactory(), (hostname, session) -> true), cachingFactory, clientFactory ); } public static SSLContext getSSLSocket(String file, String password) { SSLContext sslContext = null; try { KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); InputStream keyStoreInput = new FileInputStream(ResourceUtils.getFile(file)); keyStore.load(keyStoreInput, password.toCharArray()); KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); InputStream trustStoreInput = new FileInputStream(ResourceUtils.getFile(file)); trustStore.load(trustStoreInput, null); sslContext = SSLContexts.custom().loadKeyMaterial(keyStore, password.toCharArray()) .loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build(); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | CertificateException | IOException | UnrecoverableKeyException e) { e.printStackTrace(); } return sslContext; } @LoadBalanced @Bean public AsyncRestTemplate asyncRestTemplate() { return new AsyncRestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); } }只需要修改 bean(restTemplate、feignClient)说明与第一种方式的差异在 getSSLSocket() 方法。“`javapackage com.tsf.demo.consumer;import feign.Client;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.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.cloud.netflix.ribbon.SpringClientFactory;import org.springframework.cloud.openfeign.EnableFeignClients;import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory;import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;import org.springframework.context.annotation.Bean;import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;import org.springframework.tsf.annotation.EnableTsf;import org.springframework.web.client.AsyncRestTemplate;import org.springframework.web.client.RestTemplate;import javax.net.ssl.*;import java.security.*;import java.security.cert.X509Certificate;@SpringBootApplication@EnableFeignClients // 使用Feign微服务调用时请启用@EnableTsfpublic class ConsumerApplication { @LoadBalanced @Bean public RestTemplate restTemplate() { SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(getSSLSocket(), new String[]{"TLSv1"}, null, NoopHostnameVerifier.INSTANCE); CloseableHttpClient httpClient = HttpClients.custom() .setSSLSocketFactory(csf) .build(); HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(); requestFactory.setHttpClient(httpClient); return new RestTemplate(requestFactory); } @Bean public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory, SpringClientFactory clientFactory) { return new LoadBalancerFeignClient( new Client.Default(getSSLSocket().getSocketFactory(), (hostname, session) -> true), cachingFactory, clientFactory ); } public static SSLContext getSSLSocket() { SSLContext sslContext = null; try { sslContext = SSLContext.getInstance("TLS"); X509TrustManager tm = new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) {} @Override public void checkServerTrusted(X509Certificate[] chain, String authType) {} @Override public X509Certificate[] getAcceptedIssuers() { return null; } }; sslContext.init(null, new TrustManager[]{tm}, null); } catch (NoSuchAlgorithmException | KeyManagementException e) { e.printStackTrace(); } return sslContext; } @LoadBalanced @Bean public AsyncRestTemplate asyncRestTemplate() { return new AsyncRestTemplate(); } public static void main(String[] args) { SpringApplication.run(ConsumerApplication.class, args); }}“`
4. 验证结果。
4.1 启动 consumer-demo,浏览器访问 http://127.0.0.1:18083/echo-feign/123。
4.2 浏览器访问 http://127.0.0.1:18083/echo-rest/123。