基于tomcat的https(ssl)双向认证

news2024/11/26 10:27:16

一、背景介绍

        某个供应商服务需要部署到海外,如果海外多个地区需要部署多个服务,最好能实现统一登录,这样可以减轻用户的使用负担(不用记录一堆密码)。由于安全问题(可能会泄露用户数据),海外服务不能直连公司sso服务端,因此需要其他的方案解决安全问题。最终的安全方案中需要用到SSL双向认证进行数据的传输和交互,并且只指定某些个别接口实现SSL双向认证。在此背景下,这篇文章介绍基于tomcat的SSL双向认证的简单实现。

二、SSL简单介绍

        SSL(Secure Sockets Layer 安全套接层)就是一种协议(规范),用于保障客户端和服务器端通信的安全,以免通信时传输的信息被窃取或者修改。

        1.怎样保障数据传输安全?

        客户端和服务器端在进行握手(客户端和服务器建立连接和交换参数的过程称之为握手)时会产生一个“对话密钥”(session key),用来加密接下来的数据传输,解密时也是用的这个“对话密钥”,而这个“对话密钥”只有客户端和服务器端知道。也就是说只要这个“对话密钥”不被破解,就能保证安全。

  2. 客户端证书和服务器端证书

        客户端证书和服务器端证书用于证明自己的身份,就好比每个人都有一张身份证,这种身份证是唯一的。一般来说,只要有服务器端的证书就可以了,但是有时需要客户端提供自己的证书,已证明其身份。

三、生成自签名的服务器端证书和导入服务器端信任证书库

        一般证书可以使用权威机构颁发的证书,如:veri sign,百度使用的就是veri sign颁发的证书,这样的权威证书机构是受信任的,但是这些机构颁发的证书往往是需要收费的,这样的证书也难得到。对于小型企业来说为了节约成本,常常使用自签名的证书。   

        接下来使用JDK keytool工具来签发证书,如果未安装JDK,请先安装JDK(本文使用的是JDK8)。本文所有的证书文件都放到/cert/test1(操作系统centos),您可以选择一个目录来存放。

        1.制作服务端密钥库

keytool -genkey -v -alias server -keyalg RSA 
-keystore /cert/test1/server.keystore -validity 36500 
-ext SAN=dns:test-ssl,ip:10.1.x.x 
-dname "CN=test,OU=test,O=test,L=hz,ST=hz,C=cn"

 注意:SAN填写的是域名,IP填写是服务端IP。SAN和IP是解决谷歌浏览器证书无效的关键。

        2.制作客户端密钥库

keytool -genkey -v -alias client -keyalg RSA -storetype PKCS12 
-keystore /cert/test1/client.p12 -dname "CN=test,OU=test,O=test,L=hz,ST=hz,C=cn"

        3.客户端证书导入服务端密钥库

        由于不能直接将p12导入,需要先从客户端密钥库导出证书,再将导出的证书导入服务端密钥库。   

keytool -export -alias client -keystore /cert/test1/client.p12 
-storetype PKCS12 -storepass 123456 -rfc -file /cert/test1/client.cer

keytool -import -v -file /cert/test1/client.cer -keystore /cert/test1/server.keystore

        4.导出服务端密钥库证书 

keytool -keystore /cert/test1/server.keystore -export -alias server -file /cert/test1/server.cer

        5.配置tomcat 

        5.1配置server.xml

        找到conf目录下的server.xml文件,增加如下配置。

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" 
 SSLEnabled="true"maxThreads="150" scheme="https" secure="true"
               clientAuth="true" sslProtocol="TLS"
               keystoreFile="/cert/test1/server.keystore" keystorePass="123456"
               truststoreFile ="/cert/test1/server.keystore" truststorePass="123456"
/>

        说明:
  • clientAuth为true表示开启SSL双向认证
  • keystoreFile指定服务器端的证书位置
  •  truststoreFile指定服务器端信任证书库
         5.2配置web.xml

        找到conf目录下的server.xml文件,增加如下配置。

<security-constraint>
    <web-resource-collection>
      <web-resource-name>SSL</web-resource-name>
      <url-pattern>/ssl_test/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
      <description>SSL required</description>
      <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

        说明:
  • 如果不加入这个配置,那么所有访问的地址都必须要使用SSL才能访问,有时我们可能只需要通过某个或者某些SSL地址获取客户端证书来认证用户身份,认证成功后不需要使用SSL来进行访问(可以配置多个security-constraint)
  • url-pattern:指定需要SSL才能进行访问的地址(/ssl_test/*)
  • transport-guarantee:合法值为NONE、 INTEGRAL或CONFIDENTIAL,transport-guarantee为NONE值将对所用的通讯协议不加限制。INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。虽然原理上(并且在未来的HTTP版本中),在 INTEGRAL和CONFIDENTIAL之间可能会有差别,但在当前实践中,他们都只是简单地要求用SSL
  • 创建SSLServlet获取客户端证书

        6.编写用来获取客户端证书的filter及测试接口类

        客户端证书验证拦截器(拦截路径:/ssl_test/*)
package com.example.demo;

import java.io.IOException;
import java.security.cert.X509Certificate;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

/**
 * description:MyFilter
 *
 * @author: lgq
 * @create: 2024-02-02 10:55
 */

@WebFilter(urlPatterns = "/ssl_test/*")
public class MyFilter implements Filter {

    private static final String REQUEST_ATTR_CERT = "javax.servlet.request.X509Certificate";
    private static final String SCHEME_HTTPS = "https";

    /**
     * web应用启动时,web服务器将创建Filter的实例对象,并调用init方法,读取web.xml的配置,完成对象的初始化功能,
     * 从而为后续的用户请求做好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次,开发人员通过init的参数,
     * 可或得代表当前filter配置信息的FilterConfig对象)
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    /**
     * 这个方法完成实际的过滤操作,当客户请求访问与过滤器相关联的URL的时候,Servlet过滤器将先执行doFilter方法,FilterChain参数用于访问后续过滤器
     * @param request
     * @param response
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        X509Certificate[] certs = (X509Certificate[]) request.getAttribute(REQUEST_ATTR_CERT);
        if (certs != null) {
            int count = certs.length;
            System.out.println("共检测到[" + count + "]个客户端证书");
            for (int i = 0; i < count; i++) {
                X509Certificate cert = certs[i];
                System.out.println("客户端证书 [" + cert.getSubjectDN() + "]: ");
                System.out.println("证书是否有效:" + (verifyCertificate(cert) ? "是" : "否"));
                System.out.println("证书详细信息:\r" + cert.toString());
            }
            filterChain.doFilter(request, response);
        } else {
            if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) {
                System.out.println("这是一个HTTPS请求,但是没有可用的客户端证书");
            } else {
                System.out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表 ");
            }
        }
        System.out.println("我是过滤器,我进来了");

    }

    /**
     * filter创建后会保存在内存中,当web应用移除或者服务器停止时才销毁,该方法在Filter的生命周期中仅执行一次,在这个方法中,可以释放过滤器使用的资源
     */
    @Override
    public void destroy() {

    }

    /**
     *
     * 校验证书是否过期
     *
     *
     * @param certificate
     * @return
     */
    private boolean verifyCertificate(X509Certificate certificate) {
        boolean valid = true;
        try {
            certificate.checkValidity();
        } catch (Exception e) {
            e.printStackTrace();
            valid = false;
        }
        return valid;
    }
}
         启动类(服务部署到tomcat)
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
@ServletComponentScan
public class DemoApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(DemoApplication.class);
    }

}
        pom依赖(打war包)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <packaging>war</packaging>
    <properties>
        <java.version>8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--spring boot tomcat(默认可以不用配置,但当需要把当前web应用布置到外部servlet容器时就需要配置,并将scope配置为provided)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <finalName>test</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>2.1.1</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>
        tomcat下服务目录(工程路径/test)

        启动服务命令

        客户端ssl证书认证接口
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * description:SSLTestController 
 *
 * @author: lgq
 * @create: 2024-01-25 10:42
 */
@RestController
@RequestMapping("/ssl_test")
public class SSLTestController {


    @GetMapping("/hello")
    public String auth() {
        return "Hello, I am the server! Your client's SSL certificate has been authenticated!";
    }
}
        不需要认证客户端证书的接口
package com.example.demo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * description:NoSSLTestController
 *
 * @author: lgq
 * @create: 2024-01-25 10:42
 */
@RestController
@RequestMapping("/no_ssl_test")
public class NoSSLTestController {


    @GetMapping("/hello")
    public String auth() {
        return "Hello, I am the server!";
    }
}

        7.测试

        7.1 浏览器访问测试
        7.1.1ssl双向认证测试

        用浏览器访问http://10.1.x.x:8080/test/no_ssl_test/hello        

        细心的读者可能发现链接已经跳转到了  https://10.1.x.x:8443/test/ssl_test/hello,这是由于这个地址被设置为需要SSL才能访问,所以跳转到了这个地址。访问时页面提示如下:

        为了不出现这样的警告信息,我们可以导入服务器端证书到客户端,双击服务端证书

        选择当前用户 

        将证书放入可信任的根证书列表 ,随后安装成功

        再次访问: http://10.1.x.x:8080/test/ssl_test/hello,出现如下错误

        由于我们访问的接口是双向认证,所以也需要客户端的证书,我们接下来导入客户端证书

        自动选择证书存储 

        输入证书密钥,随即安装成功 

        第三次访问: http://10.1.x.x:8080/test/ssl_test/hello,结果如下所示

        需要选择客户端证书

        输出结果如下 

        tomcat 日志如下,(证书是否有效:是)表示客户端证书已经通过服务端验证

        7.1.2 不验证客户端证书

        访问地址http://10.1.x.x:8080/test/no_ssl_test/hello​​​​​​, 发现没有跳转到8443端口,正常返回内容如下

        7.2 测试java通过httpclient调用双向认证接口 
        1.增加apache httpclient依赖
 <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpcore -->
<dependency>
     <groupId>org.apache.httpcomponents</groupId>
     <artifactId>httpcore</artifactId>
     <version>4.4.10</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
     <groupId>org.apache.httpcomponents</groupId>
     <artifactId>httpclient</artifactId>
     <version>4.5.6</version>
</dependency>
        2.构建http请求类
package com.example.demo;

/**
 * description:HttpsRequest
 *
 * @author: lgq
 * @create: 2024-02-04 18:17
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

public class HttpsRequest {
    //.p12文件路径
    private String filePath;
    //密码
    private String passWord;
    //外呼url
    private String url;
    // 请求体
    private String body;
    //请求头
    private Map<String, String> header;
    //代理IP
    private String proxyIP;
    //代理端口
    private int proxyPort;


    private HttpsRequest(Builder builder) {
        this.filePath = builder.filePath;
        this.passWord = builder.passWord;
        this.url = builder.url;
        this.body = builder.body;
        this.header = builder.header;
        this.proxyIP = builder.proxyIP;
        this.proxyPort = builder.proxyPort;
    }

    public static class Builder {
        //.p12文件路径
        private String filePath;
        //密码
        private String passWord;
        //外呼url
        private String url;
        // 请求体
        private String body;
        //请求头
        private Map<String, String> header = new HashMap<>();
        //代理IP
        private String proxyIP;
        //代理端口
        private int proxyPort;


        public Builder filePath(String filePath) {
            this.filePath = filePath;
            return this;
        }

        public Builder passWord(String passWord) {
            this.passWord = passWord;
            return this;
        }

        public Builder url(String url) {
            this.url = url;
            return this;
        }

        public Builder body(String body) {
            this.body = body;
            return this;
        }

        public Builder header(String key, String value) {
            this.header.put(key, value);
            return this;
        }


        public Builder proxy(String ip, int port) {
            this.proxyPort = port;
            this.proxyIP = ip;
            return this;
        }

        public HttpsRequest build() {
            return new HttpsRequest(this);
        }
    }


    public String doPost() {
        String rep = "";
        SSLContext sslcontext;
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
                keyStore.load(fileInputStream, passWord.toCharArray());

                sslcontext = SSLContexts.custom()
                        //忽略掉对服务器端证书的校验
                        //.loadTrustMaterial((TrustStrategy) (chain, authType) -> true)
                        //加载服务端提供的truststore(如果服务器提供truststore的话就不用忽略对服务器端证书的校验了)
                        .loadTrustMaterial(new File("E:\\abc\\def\\server.jks"), "123456".toCharArray(),
                                new TrustSelfSignedStrategy())
                        .loadKeyMaterial(keyStore, passWord.toCharArray())
                        .build();
            }
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    sslcontext,
                    new String[]{"TLSv1"},
                    null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());


            try (CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build()) {
                HttpPost httpPost = new HttpPost(url);
                StringEntity req = new StringEntity(body, "UTF-8");
                httpPost.setEntity(req);
                if (header != null) {
                    header.entrySet().stream().forEach((h) -> {
                        httpPost.addHeader(h.getKey(), h.getValue());
                    });
                }
                RequestConfig config;
                if (!StringUtils.isEmpty(proxyIP) && !StringUtils.isEmpty(proxyPort)) {
                    HttpHost proxy = new HttpHost(proxyIP, proxyPort);
                    config = RequestConfig.custom().setProxy(proxy).setConnectionRequestTimeout(5000)
                            .setSocketTimeout(30000).setConnectTimeout(20000).build();
                } else {
                    config = RequestConfig.custom().setConnectionRequestTimeout(5000)
                            .setSocketTimeout(30000).setConnectTimeout(20000).build();
                }
                //连接超时时间, 单位毫秒
                //requestConfigBuilder.setConnectTimeout(2000);
                //从池中获取连接超时时间
                //requestConfigBuilder.setConnectionRequestTimeout(500);
                //读超时时间(等待数据超时时间)
                //requestConfigBuilder.setSocketTimeout(2000);

                httpPost.setConfig(config);


                try (CloseableHttpResponse httpResponse = client.execute(httpPost)) {
                    HttpEntity entity = httpResponse.getEntity();
                    rep = EntityUtils.toString(entity);
                }
            }
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        }
        return rep;
    }

    public String doGet() {
        String rep = "";
        SSLContext sslcontext;
        try {
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            try (FileInputStream fileInputStream = new FileInputStream(filePath)) {
                keyStore.load(fileInputStream, passWord.toCharArray());

                sslcontext = SSLContexts.custom()
                        //忽略掉对服务器端证书的校验
                        //.loadTrustMaterial((TrustStrategy) (chain, authType) -> true)
                        //加载服务端提供的truststore(如果服务器提供truststore的话就不用忽略对服务器端证书的校验了)
                        .loadTrustMaterial(new File("E:\\abc\\def\\server.jks"), "123456".toCharArray(),
                                new TrustSelfSignedStrategy())
                        .loadKeyMaterial(keyStore, passWord.toCharArray())
                        .build();
            }
            SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(
                    sslcontext,
                    new String[]{"TLSv1.2"},
                    null,
                    SSLConnectionSocketFactory.getDefaultHostnameVerifier());


            try (CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build()) {
                HttpGet httpGet = new HttpGet(url);
                if (!ObjectUtils.isEmpty(header)) {
                    header.entrySet().stream().forEach((h) -> {
                        httpGet.addHeader(h.getKey(), h.getValue());
                    });
                }
                RequestConfig config;
                if (!ObjectUtils.isEmpty(proxyIP) && !ObjectUtils.isEmpty(proxyPort)) {
                    HttpHost proxy = new HttpHost(proxyIP, proxyPort);
                    config = RequestConfig.custom().setProxy(proxy).setConnectionRequestTimeout(5000)
                            .setSocketTimeout(30000).setConnectTimeout(20000).build();
                } else {
                    config = RequestConfig.custom().setConnectionRequestTimeout(5000)
                            .setSocketTimeout(30000).setConnectTimeout(20000).build();
                }
                //连接超时时间, 单位毫秒
                //requestConfigBuilder.setConnectTimeout(2000);
                //从池中获取连接超时时间
                //requestConfigBuilder.setConnectionRequestTimeout(500);
                //读超时时间(等待数据超时时间)
                //requestConfigBuilder.setSocketTimeout(2000);

                httpGet.setConfig(config);


                try (CloseableHttpResponse httpResponse = client.execute(httpGet)) {
                    HttpEntity entity = httpResponse.getEntity();
                    rep = EntityUtils.toString(entity);
                }
            }
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        } catch (UnrecoverableKeyException e) {
            e.printStackTrace();
        }
        return rep;
    }

}
3.将服务端证书由 cer格式转为jks
keytool -import -alias server -file /cert/test1/server.cer -keystore /cert/test1/server.jks
 4.测试双向认证请求
package com.example.demo;

/**
 * description:HttpsRequestTest
 *
 * @author: lgq
 * @create: 2024-02-04 18:24
 */
public class HttpsRequestTest {
    public static void main(String[] args) {
        String result = new HttpsRequest.Builder()
                .filePath("E:\\abc\\def\\client.p12")
                .passWord("123456")
                .url("https://10.1.x.x:8443/test/ssl_test/hello")
                .header("charset", "UTF-8")//头信息,多个头信息多次调用此方法即可
                .build()
                .doGet();
        System.out.println(result);
    }
}

        输出结果

"C:\Program Files\Java\jdk1.8.0_101\bin\java.exe" -Dvisualvm.id=375771477320700 "-javaagent:E:\software-tools\JetBrains\IntelliJ IDEA 2020.3.3\lib\idea_rt.jar=55001:E:\software-tools\JetBrains\IntelliJ IDEA 2020.3.3\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_101\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar;D:\project\demo1\target\classes;D:\maven\repository\org\springframework\boot\spring-boot-starter-web\2.4.3\spring-boot-starter-web-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot-starter\2.4.3\spring-boot-starter-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot\2.4.3\spring-boot-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.3\spring-boot-autoconfigure-2.4.3.jar;D:\maven\repository\org\springframework\boot\spring-boot-starter-logging\2.4.3\spring-boot-starter-logging-2.4.3.jar;D:\maven\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\maven\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\maven\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;D:\maven\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;D:\maven\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;D:\maven\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;D:\maven\repository\org\springframework\spring-core\5.3.4\spring-core-5.3.4.jar;D:\maven\repository\org\springframework\spring-jcl\5.3.4\spring-jcl-5.3.4.jar;D:\maven\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;D:\maven\repository\org\springframework\boot\spring-boot-starter-json\2.4.3\spring-boot-starter-json-2.4.3.jar;D:\maven\repository\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;D:\maven\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;D:\maven\repository\org\springframework\spring-web\5.3.4\spring-web-5.3.4.jar;D:\maven\repository\org\springframework\spring-beans\5.3.4\spring-beans-5.3.4.jar;D:\maven\repository\org\springframework\spring-webmvc\5.3.4\spring-webmvc-5.3.4.jar;D:\maven\repository\org\springframework\spring-aop\5.3.4\spring-aop-5.3.4.jar;D:\maven\repository\org\springframework\spring-context\5.3.4\spring-context-5.3.4.jar;D:\maven\repository\org\springframework\spring-expression\5.3.4\spring-expression-5.3.4.jar;D:\maven\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;D:\maven\repository\org\apache\httpcomponents\httpcore\4.4.10\httpcore-4.4.10.jar;D:\maven\repository\org\apache\httpcomponents\httpclient\4.5.6\httpclient-4.5.6.jar;D:\maven\repository\commons-codec\commons-codec\1.15\commons-codec-1.15.jar" com.example.demo.HttpsRequestTest
18:55:06.389 [main] DEBUG org.apache.http.client.protocol.RequestAddCookies - CookieSpec selected: default
18:55:06.411 [main] DEBUG org.apache.http.client.protocol.RequestAuthCache - Auth cache not set in the context
18:55:06.413 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection request: [route: {s}->https://10.1.x.x:8443][total kept alive: 0; route allocated: 0 of 2; total allocated: 0 of 20]
18:55:06.429 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection leased: [id: 0][route: {s}->https://10.1.x.x:8443][total kept alive: 0; route allocated: 1 of 2; total allocated: 1 of 20]
18:55:06.431 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Opening connection {s}->https://10.1.x.x:8443
18:55:06.434 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connecting to /10.1.x.x:8443
18:55:06.434 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Connecting socket to /10.1.x.x:8443 with timeout 20000
18:55:06.491 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Enabled protocols: [TLSv1.2]
18:55:06.491 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
18:55:06.491 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Starting handshake
18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory - Secure session established
18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory -  negotiated protocol: TLSv1.2
18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory -  negotiated cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
18:55:06.598 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory -  peer principal: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn
18:55:06.599 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory -  peer alternative names: [test-ssl, 10.1.x.x]
18:55:06.599 [main] DEBUG org.apache.http.conn.ssl.SSLConnectionSocketFactory -  issuer principal: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn
18:55:06.605 [main] DEBUG org.apache.http.impl.conn.DefaultHttpClientConnectionOperator - Connection established 10.26.54.125:55008<->10.1.x.x:8443
18:55:06.605 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 30000
18:55:06.606 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Executing request GET /test/ssl_test/hello HTTP/1.1
18:55:06.606 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
18:55:06.607 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
18:55:06.609 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> GET /test/ssl_test/hello HTTP/1.1
18:55:06.609 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> charset: UTF-8
18:55:06.609 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Host: 10.1.x.x:8443
18:55:06.610 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Connection: Keep-Alive
18:55:06.610 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_101)
18:55:06.610 [main] DEBUG org.apache.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "GET /test/ssl_test/hello HTTP/1.1[\r][\n]"
18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "charset: UTF-8[\r][\n]"
18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Host: 10.1.x.x:8443[\r][\n]"
18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_101)[\r][\n]"
18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
18:55:06.610 [main] DEBUG org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Cache-Control: private[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Type: text/plain;charset=UTF-8[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Content-Length: 77[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Date: Sun, 04 Feb 2024 10:55:07 GMT[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Keep-Alive: timeout=60[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
18:55:06.619 [main] DEBUG org.apache.http.wire - http-outgoing-0 << "Hello, I am the server! Your client's SSL certificate has been authenticated!"
18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << HTTP/1.1 200 
18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Cache-Control: private
18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Type: text/plain;charset=UTF-8
18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Content-Length: 77
18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Date: Sun, 04 Feb 2024 10:55:07 GMT
18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Keep-Alive: timeout=60
18:55:06.624 [main] DEBUG org.apache.http.headers - http-outgoing-0 << Connection: keep-alive
18:55:06.632 [main] DEBUG org.apache.http.impl.execchain.MainClientExec - Connection can be kept alive for 60000 MILLISECONDS
18:55:06.640 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection [id: 0][route: {s}->https://10.1.x.x:8443][state: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn] can be kept alive for 60.0 seconds
18:55:06.640 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: set socket timeout to 0
18:55:06.640 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection released: [id: 0][route: {s}->https://10.1.x.x:8443][state: CN=test, OU=test, O=test, L=hz, ST=hz, C=cn][total kept alive: 1; route allocated: 1 of 2; total allocated: 1 of 20]
18:55:06.641 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager is shutting down
18:55:06.641 [main] DEBUG org.apache.http.impl.conn.DefaultManagedHttpClientConnection - http-outgoing-0: Close connection
18:55:06.642 [main] DEBUG org.apache.http.impl.conn.PoolingHttpClientConnectionManager - Connection manager shut down
Hello, I am the server! Your client's SSL certificate has been authenticated!

Process finished with exit code 0

        其中,输出内容:“Hello, I am the server! Your client's SSL certificate has been authenticated!”,表示客户端证书认证已经通过。需要注意的是,上面方法中,客户端证书使用的是p12格式,服务端证书使用jks格式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1433410.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s学习-Kubernetes的包管理器Helm

1.1 为何需要Helm Kubernetes能够很好地组织和编排容器&#xff0c;但它缺少⼀个更高层次的应用打包工具&#xff0c;而Helm就是来干这件事的。 先来看个例子。 比如对于⼀个MySQL服务&#xff0c;Kubernetes需要部署下面这些对象&#xff1a; &#xff08;1&#xff09;Serv…

2. 从波动方程到亥姆赫兹方程

波动方程中同时包含了时间和空间分量&#xff0c;为进一步简化波动方程&#xff0c;可以假设电场分量为 &#xff08;1&#xff09; &#xff08;注&#xff1a;这个假设对我而言有点突兀&#xff0c;但我想对于数学好的人来说就是一个常见的解题思路。可能就像高中数列题&…

【开源】JAVA+Vue+SpringBoot实现二手车交易系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 二手车档案管理模块2.3 车辆预约管理模块2.4 车辆预定管理模块2.5 车辆留言板管理模块2.6 车辆资讯管理模块 三、系统设计3.1 E-R图设计3.2 可行性分析3.2.1 技术可行性分析3.2.2 操作可行性3.2.3 经济…

用于医疗行业的大功率电阻器是什么?

设计医疗电子设备比设计其他行业应用更具挑战性。由于涉及宝贵的生命&#xff0c;因此各种医疗设备中使用的产品和组件必须绝对可靠且安全&#xff0c;毋庸置疑。即使是各种国际机构制定的合规和安全标准&#xff0c;在医疗保健行业也更加严格&#xff0c;这是正确的。 无源电气…

Re-understanding of data storytelling tools from a narrative perspective

作者&#xff1a;任芃锟, 王轶 & 赵凡 发表&#xff1a;Visual Intelligence&#xff0c;新刊&#xff0c;实行单盲同行评议制度。由施普林格以开放获取 (Open Access) 模式出版。获2022“中国科技期刊卓越行动计划高起点新刊”项目资助&#xff0c;目前出版不收取文章处理…

onlyfans无法订阅?2024年订阅onlyfans最新教程一键直达

讲在前面-关于OnlyFans 欧美除了脸书和推特之外&#xff0c;又新起了一个社交软件&#xff0c;它就是onlyfans&#xff0c;简称o站。 在极短的时间内&#xff0c;它就拥有了1.2亿的用户量&#xff0c;而全站订阅金额更是达到了17亿英镑&#xff0c;换成人民币&#xff0c;数额…

使用docker/docker-compose通过自定义的redis.conf文件启动redis 7.2.3,附上docker-compose.yml的redis配置

目录 一.复制以及使用自定义的redis.conf文件 1.在官网拷贝对应版本的配置文件内容新建redis.conf文件进行粘贴。&#xff08;推荐&#xff09; 2.也可以去官网下载对应版本的redis的tar.gz包&#xff0c;解压后在根目录下找到redis.conf文件复制也可也可。 二.配置redis.c…

机器学习复习(8)——基本概念

目录 "benchmark"和"baseline"的定义和区别 R1 score概念 LoRA微调概念 "benchmark"和"baseline"的定义和区别 在计算机视觉领域的论文中&#xff0c;"benchmark"和"baseline"这两个术语经常被使用&#xff0…

BootStrap学习笔记JS插件(一)--模态弹出框

一、弹出框基础 <div class"modal show"><div class"modal-dialog"><div class"modal-content"><div class"modal-header"><button type"button" class"close" data-dismiss"mo…

惠普公司也要注销了?

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 惠普科技(上海)有限公司企业状态由存续变更为注销&#xff0c;这意味着惠普公司也要注销了?这是怎么回事?戴尔公司也准备注销了呢?这家美国科技巨头为什么放弃了世界最大的消费市场呢? 之前就有消息称惠普中国…

BFS——双向广搜+A—star

有时候从一个点能扩展出来的情况很多&#xff0c;这样几层之后搜索空间就很大了&#xff0c;我们采用从两端同时进行搜索的策略&#xff0c;压缩搜索空间。 190. 字串变换(190. 字串变换 - AcWing题库) 思路&#xff1a;这题因为变化规则很多&#xff0c;所以我们一层一层往外…

新开发板-正点原子的rk3568

有好长一段时间没有更新博客了&#xff0c;上次更新还是在上次...哈哈开个玩笑&#xff0c;上次stm32f407的定时器还没写完&#xff0c;就备战期末去了&#xff08;电信学院&#xff0c;你懂的&#xff09;&#xff0c;一直没更新&#xff0c;原因是我实习去了&#xff0c;在忙…

Java项目管理01-Maven基础

一、Maven的常用命令和生命周期 1.Maven的常用命令使用方式 complie&#xff1a;编译&#xff0c;将java文件编译为class字节码文件 clean&#xff1a;清理&#xff0c;删除字节码文件 test&#xff1a;测试&#xff0c;运行项目中的test类 package&#xff1a;打包&#x…

IDEA新建文件夹后右击不能创建class类排错方法

目录 1 查看自身文件名是否为关键词 2 查看是否被“蓝色文件夹”给包含了 3 检查设置那边的class模板 4 报错解决 1 查看自身文件名是否为关键词 如下使用了 Java中的关键词"class"所以才无法创建包 ---------------------------------------------------------…

51单片机之LED灯模块篇

御风以翔 破浪以飏 &#x1f3a5;个人主页 &#x1f525;个人专栏 目录 点亮一盏LED灯 LED的组成原理 LED的硬件模型 点亮一盏LED灯的程序设计 LED灯闪烁 LED流水灯 独立按键控制LED灯亮灭 独立按键的组成原理 独立按键的硬件模型 独立按键控制LED灯状态 按键的抖动 独立按键…

KubeMQ简介

如今&#xff0c;企业组织之间的竞争是残酷的。每个组织都希望在其系统之间即时、实时或近乎实时地交换信息&#xff0c;以便做出更好、更快的决策。为了使此类信息持续流动&#xff0c;应用程序组件之间的集成需要无缝。为了充分利用云计算的所有优势&#xff0c;如今构建的应…

双非本科准备秋招(16.1)—— 力扣二叉树

1、101. 对称二叉树 检查是否对称&#xff0c;其实就是检查左节点等不等于右节点&#xff0c;我们可以用递归来做。 如果左右节点都为null&#xff0c;说明肯定对称呀&#xff0c;返回true。 如果一个为null一个不为null&#xff0c;或者左右的值不相等&#xff0c;则为false。…

k8s-深入理解Service(为Pod提供负载均衡和发现)

一、Service存在的意义 二、Service的定义和创建 Pod与Service的关系 Service的定义和创建 三、Service使用NodePort对外暴露应用 四种类型&#xff0c;常用的三种&#xff1a; 指定Service的NodePort端口 在实际生产中&#xff0c;k8s的集群不会直接暴露在公网中&#xff0c…

free5GC+UERANSIM

使用arp、ifconfig、docker inspect及网桥brctl 相关命令&#xff0c;收集容器IP及Mac地址相关信息&#xff0c;可以梳理出UERANSIMfree5GC模拟环境组网&#xff0c;如下图所示&#xff1a; 如上图所示&#xff1a;环境基于ubuntu 18.04 VMware虚机部署&#xff0c;5GC网元分别…

【Redis】字符串原理--简单动态字符串SDS

一.SDS定义 free 属性值为0&#xff0c;标识SDS没有分配任何未使用空间。len 属性值为5&#xff0c;标识SDS保存了一个5字节长度的字符串。buf 属性是一个char类型数组&#xff0c;数组的前5个字节保存了&#xff0c;R e d i s 五个字符&#xff0c;最后一个保存空字符串 \0…