IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 使用WebClient和RestTemplate访问HTTPS -> 正文阅读

[网络协议]使用WebClient和RestTemplate访问HTTPS

公司一个SpringBoot系统需要HTTPS改造,要求HHTP、HTTPS单向、HTTPS双向都是可配置的,它们是由四个系统构成的,相互之间通过WebClient和RestTemplate进行请求。所以首先我们可以明确配置文件的内容:

server:
  port: 8080
  ##Https改造, 若不是https则将enabled置为false, 若是则上传对应的证书,完善正确的配置
  ssl:
    # 是否开启SSL
    enabled: true
    key-store: server.jks
    key-alias: localhost
    key-store-password: 123456
    key-store-type: JKS

    trust-store-provider: SUN
    trust-store-type: JKS
    trust-store: server.jks
    #密码,即步骤一中输入的密码
    trust-store-password: 123456
    #是否是双向认证, need代表开启双向认证、want尝试认证,成功与否都能建立连接、none不进行认证
    client-auth: need
  # 若是选择开启双向认证, 则需配置客户端的文件的信息
  ssl-client:
    client-file: client.jks
    client-type: JKS
    client-password: 123456

其中:

enabled:              是否开启https,默认为False,则是正常的HTTP
key-store-type:      服务端证书的类型 JKS / pcks12
key-store:           服务端证书文件的位置
key-store-password:  服务端证书的密码
key-alias:           服务端证书的别名

trust-store:         信任证书的文件位置,与服务端相同即可
trust-store-password:证书的密码
trust-store-provider: SUN
trust-store-type:     证书类型

client-auth:         是否需要验证客户端证书、默认值为none;need代
                      表开启双向认证、want尝试认证,成功与否都能建立
                      连接、none不进行认证

client-file:         客户端证书文件
client-type:         客户端证书类型
client-password:     客户端证书密码
# 会发现trust和key的配置内容相同

而后在系统初始化的时候,根据配置文件的内容选择什么样的WebClient和RestTemplate

1. 当enabled为flase
则WebClient 为  WebClient.create()
则RestTemplate为:
int POOL_SIZE = 200;
int TIMEOUT = 10000;
RequestConfig defaultRequestConfig = RequestConfig.custom().setSocketTimeout(TIMEOUT).setConnectTimeout(TIMEOUT)
                .setConnectionRequestTimeout(TIMEOUT).build();
PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager();
        connMgr.setMaxTotal(POOL_SIZE + 1);
        connMgr.setDefaultMaxPerRoute(POOL_SIZE);
CloseableHttpClient httpClient =
                HttpClients.custom().setConnectionManager(connMgr).setDefaultRequestConfig(defaultRequestConfig).build();
RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpClient));
List<HttpMessageConverter<?>> converters = new ArrayList<>();
FastJsonHttpMessageConverter fastjson = new FastJsonHttpMessageConverter();
converters.add(fastjson);
template.setMessageConverters(converters);
return template;
2. 当client-auth等于none或者是want,表示单向认证

则WebClient 为:
reactor.netty.http.client.HttpClient secure = HttpClient.create()
                    .secure(t -> t.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)));
return WebClient.builder()
                    .clientConnector(new ReactorClientHttpConnector(secure))
                    .build();

则RestTemplate为:
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;

SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy)
                .build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
return new RestTemplate(requestFactory);
3. 否则为双向认证,即client-auth=need
注意:以下代码里的常量需替换为自己获取到的值,如类型,文件路径,密码

则WebClient 为:

private static WebClient createWebClient() {
File clientFile = new File(Constant.HTTP_SSL_FILE_PATH);
            if (!clientFile.exists()) {
                throw new PaException("请上传客户端证书: " + Constant.HTTP_SSL_FILE_PATH);
            }
            InputStream is = new FileInputStream(clientFile);
            KeyStore ks = KeyStore.getInstance(Constant.HTTP_SSL_FILE_TYPE);
            ks.load(is, Constant.HTTP_SSL_PASSWORD.toCharArray());
            log.info("管理平台为HTTPS双向验证, 证书为: " + Constant.HTTP_SSL_FILE_PATH);
            return WebClient.builder().clientConnector(getClientHttpConnector(ks, Constant.HTTP_SSL_PASSWORD, ks)).build();
}

private static ClientHttpConnector getClientHttpConnector(KeyStore keyStore, String keyStorePassword, KeyStore trustStore) throws Exception {
        SslContextBuilder builder = SslContextBuilder.forClient();
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
        builder.keyManager(keyManagerFactory);

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
        trustManagerFactory.init(trustStore);
        builder.trustManager(trustManagerFactory);
        SslContext sslContext = builder.build();
        HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext).handlerConfigurator(handler -> {
            SSLEngine engine = handler.engine();
            List<SNIMatcher> matchers = new LinkedList<>();
            SNIMatcher matcher = new SNIMatcher(0) {
                @Override
                public boolean matches(SNIServerName serverName) {
                    return true;
                }
            };
            matchers.add(matcher);
            SSLParameters params = new SSLParameters();
            params.setSNIMatchers(matchers);
            engine.setSSLParameters(params);
        }));
        return new ReactorClientHttpConnector(httpClient);
    }

则RestTemplate为:

private static String pwd;

private static String clientNeed;

private static String clientFile;

private static String clientType;

/**
 * @Value 无法作用于静态变量, 采用set方法进行解决
 */
@Value("${server.ssl-client.client-password:null}")
public void setPwd(String pwd) {
    MafitRestTemplateConfig.pwd = pwd;
}

@Value("${server.ssl.client-auth:none}")
public void setClientNeed(String clientNeed) {
    MafitRestTemplateConfig.clientNeed = clientNeed;
}

@Value("${server.ssl-client.client-file:null}")
public void setClientFile(String clientFile) {
    MafitRestTemplateConfig.clientFile = clientFile;
}

@Value("${server.ssl-client.client-type:null}")
public void setClientType(String clientType) {
    MafitRestTemplateConfig.clientType = clientType;
}
public static RestTemplate restTemplateSsl(String password) throws Exception {
        //创建一个双向验证的RestTemplate  param: password 密码
        FastJsonHttpMessageConverter httpMessageConverter = new FastJsonHttpMessageConverter();
        HttpComponentsClientHttpRequestFactory factory = new
                HttpComponentsClientHttpRequestFactory();
        factory.setConnectionRequestTimeout(5 * 60 * 1000);
        factory.setConnectTimeout(5 * 60 * 1000);
        factory.setReadTimeout(5 * 60 * 1000);
        SSLContextBuilder builder = new SSLContextBuilder();
        KeyStore keyStore = KeyStore.getInstance(clientType);
        File file = new File(clientFile);
        if (!file.exists()) {
            throw new PaException("未加载到客户端文件" + clientFile);
        }
        InputStream inputStream = new FileInputStream(clientFile);
        keyStore.load(inputStream, password.toCharArray());
        builder.loadKeyMaterial(keyStore, password.toCharArray());
        builder.loadTrustMaterial(keyStore, null);
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(builder.build(), NoopHostnameVerifier.INSTANCE);
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new PlainConnectionSocketFactory())
                .register("https", socketFactory).build();
        PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(registry);
        phccm.setMaxTotal(200);
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).setConnectionManager(phccm).setConnectionManagerShared(true).build();
        factory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate(factory);
        List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
        ArrayList<HttpMessageConverter<?>> convertersValid = new ArrayList<>();
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter ||
                    converter instanceof MappingJackson2XmlHttpMessageConverter) {
                continue;
            }
            convertersValid.add(converter);
        }
        convertersValid.add(httpMessageConverter);
        restTemplate.setMessageConverters(convertersValid);
        inputStream.close();
        return restTemplate;
    }
# 直接 return restTemplateSsl(pwd);即可 在创建之前,先执行判断语句,来验证参数的正确性
private static void setLoadHttpTypeInit() {
	    LoadHttpType.SSL_HTTP_TYPE = LoadHttpType.HTTP_TYPE_DEFAULT;
	    if (mgrUrl.startsWith(LoadHttpType.HTTPS_TYPE_DEFAULT)) {
	        //https的形式
	        LoadHttpType.SSL_HTTP_TYPE = LoadHttpType.HTTPS_TYPE_DEFAULT;
	        LoadHttpType.SSL_IS_NEED_CLIENT = "need".equals(clientNeed);
	        if (LoadHttpType.SSL_IS_NEED_CLIENT) {
	            //双向验证  nullMsg: "null"
	            if (nullMsg.equals(clientType) || nullMsg.equals(pwd) || nullMsg.equals(clientFile)) {
	                throw new PaException("客户端证书的信息配置缺失, 请检查server.ssl-client中的配置信息");
	            }
	        }
	    }
}

生成证书方法参考

  • 若是证书在服务器中执行

步骤1:在windows下执行,其中:

Localhost: 证书的别名
localhost.jks: 生成的证书的名字,可以不修改,生成完毕之后再重命名
CN=127.0.0.1和 san=ip:127.0.0.1 :
若是本地测试,则填写127.0.0.1,若是放在服务器上运行,则填写服务器的ip,系统运行在哪台服务器,就填写哪个ip
123456:密码

keytool -genkey -alias localhost -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore localhost.jks -dname CN=127.0.0.1,OU=Test,O=pkslow,L=Guangzhou,C=CN -ext san=ip:127.0.0.1 -validity 36000 -storepass 123456 -keypass 123456

步骤2:在windows下执行

keytool -export -alias localhost -file localhost.cer -keystore localhost.jks

步骤3:在windows下执行

keytool -genkey -alias client -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore client.jks -dname CN=client,OU=Test,O=pkslow,L=Guangzhou,C=CN -validity 731 -storepass 123456 -keypass 123456

步骤4:在windows下执行

keytool -export -alias client -file client.cer -keystore client.jks

步骤5:将生成的证书放入服务器的目录下,在服务器中执行以下代码

keytool -import -alias client -file client.cer -keystore localhost.jks

会询问是否添加到信任库中,填写y回车即可

步骤6:在服务器中执行以下代码

keytool -import -alias localhost -file localhost.cer -keystore client.jks

步骤7:生成完毕,需要用到的文件为:localhost.jks 和 client.jks , 可以将localhost.jks重命名为server.jks

打开浏览器 -> 设置 -> 安全 -> 管理证书 ->导入 -> 浏览器
点击所有文件->选择client.jks->下一步输入密码…即可将证书导入服务器中
在这里插入图片描述

  1. 问:为什么要分在windows下执行 和 linux下执行代码?

答:若是全部在linux下执行,是可以执行成功的,但是发现了一个问题,就是从服务器中拿到client.jks证书,再导入进浏览器的时候会提示以下信息:
在这里插入图片描述

而代码中使用这个证书进行请求,却是正常的。不知道别人会不会出现,总之为了防止这个事情发生,故生成证书放在客户端来执行,而将证书添加到信任库中,则是在服务端执行。

  1. 问:为什么要在linux上执行那两个代码?

答:为了将client添加进服务器的信任库中。否则请求会出现No trusted certificate found错误

  1. 问:如何进行测试,知道自己的配置是否正确?

答:我们要求各个系统的证书是设置为一样的,因为不同系统会同时请求其它系统,若是需要的客户端证书不一致,则无法区分开具体使用哪个client文件。

执行以下Test.java代码请求mgr,测试证书是否正确

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
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.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;

import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * @author 
 * @Date: 创建于 10:47 2021/9/16
 * @说明:
 */
public class Test {
    static String url = "https://IP:端口/xxxx";
    static String clickFile = "client.jks";
    static String password = "123456";
    static String alias = "JKS";
    public static void main(String[] args) throws Exception {
        File clientFile = new File(clickFile);
        if (!clientFile.exists()) {
            throw new Exception("请上传客户端证书");
        }
        InputStream is = new FileInputStream(clientFile);
        KeyStore ks = KeyStore.getInstance(alias);
        ks.load(is, password.toCharArray());
        WebClient webClient =  WebClient.builder().clientConnector(getClientHttpConnector(ks, password, ks)).build();
        System.out.println(webClient.get().uri(
                url
        ).retrieve().bodyToMono(String.class).block());

        RestTemplate restTemplate = getRestTemplate();
        System.out.println(restTemplate.getForObject(url, String.class));
    }

    private static ClientHttpConnector getClientHttpConnector(KeyStore keyStore, String keyStorePassword, KeyStore trustStore) throws Exception {
        SslContextBuilder builder = SslContextBuilder.forClient();
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
        builder.keyManager(keyManagerFactory);

        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
        trustManagerFactory.init(trustStore);
        builder.trustManager(trustManagerFactory);
        SslContext sslContext = builder.build();
        HttpClient httpClient = HttpClient.create().secure(t -> t.sslContext(sslContext).handlerConfigurator(handler -> {
            SSLEngine engine = handler.engine();
            List<SNIMatcher> matchers = new LinkedList<>();
            SNIMatcher matcher = new SNIMatcher(0) {
                @Override
                public boolean matches(SNIServerName serverName) {
                    return true;
                }
            };
            matchers.add(matcher);
            SSLParameters params = new SSLParameters();
            params.setSNIMatchers(matchers);
            engine.setSSLParameters(params);
        }));
        return new ReactorClientHttpConnector(httpClient);
    }

    public static RestTemplate getRestTemplate() throws Exception {
        FastJsonHttpMessageConverter httpMessageConverter = new FastJsonHttpMessageConverter();
        HttpComponentsClientHttpRequestFactory factory = new
                HttpComponentsClientHttpRequestFactory();
        factory.setConnectionRequestTimeout(5 * 60 * 1000);
        factory.setConnectTimeout(5 * 60 * 1000);
        factory.setReadTimeout(5 * 60 * 1000);
        // https
        SSLContextBuilder builder = new SSLContextBuilder();
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        File clientFile = new File(clickFile);
        InputStream inputStream = new FileInputStream(clientFile);
        keyStore.load(inputStream, password.toCharArray());

        builder.loadKeyMaterial(keyStore, password.toCharArray());
        builder.loadTrustMaterial(keyStore,null);

        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(builder.build(), NoopHostnameVerifier.INSTANCE);
        Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", new PlainConnectionSocketFactory())
                .register("https", socketFactory).build();
        PoolingHttpClientConnectionManager phccm = new PoolingHttpClientConnectionManager(registry);
        phccm.setMaxTotal(200);
        CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).setConnectionManager(phccm).setConnectionManagerShared(true).build();
        factory.setHttpClient(httpClient);
        RestTemplate restTemplate = new RestTemplate(factory);
        List<HttpMessageConverter<?>> converters = restTemplate.getMessageConverters();
        ArrayList<HttpMessageConverter<?>> convertersValid = new ArrayList<>();
        for (HttpMessageConverter<?> converter : converters) {
            if (converter instanceof MappingJackson2HttpMessageConverter ||
                    converter instanceof MappingJackson2XmlHttpMessageConverter) {
                continue;
            }
            convertersValid.add(converter);
        }
        convertersValid.add(httpMessageConverter);
        restTemplate.setMessageConverters(convertersValid);
        inputStream.close();
        return restTemplate;
    }
}

注: 生成证书借鉴了博客《Https双向验证与Springboot整合测试-人来人往我只认你
编写请求部分内容借鉴了博客《Java调用Http/Https接口(7)–WebClient调用Http/Https接口

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2021-09-19 08:19:50  更:2021-09-19 08:20:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 1:21:06-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码