前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Nginx配置Https单向认证、双向认证以及多证书配置

Nginx配置Https单向认证、双向认证以及多证书配置

原创
作者头像
花落花相惜
修改2021-11-24 14:22:26
10.2K0
修改2021-11-24 14:22:26
举报
文章被收录于专栏:花落的技术专栏

1、环境准备

1.1、Nginx

Nginx版本:1.12.0

Nginx为了支持Https需要安装http_ssl_module模块。在编译时需要带上--with-http_ssl_module参数。

代码语言:txt
复制
./configure --prefix=/usr/local/nginx --with-http_ssl_module
代码语言:txt
复制
make && make install

然后通过./nginx -V查看有没有--with-http_ssl_module参数。

1.2、openssl生成公私钥

无论是客户端还是服务端,都可以使用openssl命令来生成公私钥,前提是需要安装好openssl

生成服务端私钥

代码语言:txt
复制
openssl genrsa [-out filename] [numbits]

比如:生成一个名为server.key的私钥,长度1024。

代码语言:txt
复制
openssl genrsa -out server.key 1024

openssl

生成服务端公钥证书

代码语言:txt
复制
openssl req -new -x509 [-key keyfile] [-out crtfile] [-days numdays]

比如:生成一个名为server.crt的证书,有效期10年。

代码语言:txt
复制
openssl req -new -x509 -key server.key -out server.crt -days 3650

依次会要求输入国家、省市、公司单位、域名、邮箱等信息。

最关键的是域名信息`Common

Name,这里需要填写服务器的域名地址,比如test.com;也可以填写泛域名,比如*.test.com`;如果没有域名,可以直接填写服务端ip地址。

openssl

通过以上2步,已经生成了私钥server.key,公钥证书server.crt

生成客户端公私钥

代码语言:txt
复制
# 生成客户端证书私钥
代码语言:txt
复制
openssl genrsa -out client.key 1024
代码语言:txt
复制
# 生成客户端公钥证书
代码语言:txt
复制
openssl req -new -x509 -key client.key -out client.crt -days 3650
代码语言:txt
复制
# 生客户端p12格式证书,需要输入一个密码,选一个好记的,比如123456
代码语言:txt
复制
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

最后会将公私钥两个文件合成得到一个p12文件,p12文件主要用于客户端(包括Postman、浏览器、Java客户端等)发起https请求提供公私钥。

还可以利用Java自带的keytool工具来生成公私钥,并且可以和openssl生成的公私钥进行互相转换。具体可以参考文末的附录。

2、单向认证配置和客户端调用

2.1、Nginx配置

编辑nginx.conf文件在http{...}配置块中新增一个server配置块。

代码语言:txt
复制
# 负载均衡配置
代码语言:txt
复制
upstream backend {
代码语言:txt
复制
    server 192.168.0.1:10900;
代码语言:txt
复制
    server 192.168.0.2:10900;
代码语言:txt
复制
    server 192.168.0.3:10900;
代码语言:txt
复制
}
代码语言:txt
复制
server {
代码语言:txt
复制
    listen       21000 ssl;
代码语言:txt
复制
    server_name  localhost;
代码语言:txt
复制
    ssl_certificate      ../ssl/server.crt;  # server公钥证书
代码语言:txt
复制
    ssl_certificate_key  ../ssl/server.key;  # server私钥
代码语言:txt
复制
    ssl_session_cache    shared:SSL:1m;
代码语言:txt
复制
    ssl_session_timeout  5m;
代码语言:txt
复制
    ssl_ciphers  HIGH:!aNULL:!MD5;
代码语言:txt
复制
    ssl_prefer_server_ciphers  on;
代码语言:txt
复制
    access_log logs/access1.log; # 单独指定输出访问日志
代码语言:txt
复制
    error_log logs/error1.log; # # 单独指定输出错误日志
代码语言:txt
复制
    location / {
代码语言:txt
复制
        proxy_pass    http://backend;
代码语言:txt
复制
    }
代码语言:txt
复制
}

单向认证只需要配置服务器的公私钥即可,这里的相对路径是相对Nginx的配置文件nginx.conf的路径而言的。而输出日志的相对路径是相对于conf目录的路径而言。

2.2、Postman调用

只需要把原来请求的http替换成https即可,不需要做其它任何改动。

2.3、浏览器调用

也是把http替换成https就行了。

如果浏览器提示有风险NET::ERR_CERT_AUTHORITY_INVALID,这主要是服务端证书是我们自己通过命令生成签发的,不是来自于正规的CA机构签发,所以导致浏览器不信任,可以点击

高级 - > 继续前往即可。

注意这里最好使用谷歌浏览器测试 ,国内一些浏览器可能会提示NET::ERR_CERT_REVOKED证书已被吊销,达不到测试效果。

2.4、Java客户端调用

网上很多都是采用httpclient作为http库,这里比较推荐使用Hutool工具包。

代码语言:txt
复制
<dependency>
代码语言:txt
复制
    <groupId>cn.hutool</groupId>
代码语言:txt
复制
    <artifactId>hutool-all</artifactId>
代码语言:txt
复制
    <version>5.2.4</version>
代码语言:txt
复制
</dependency>
代码语言:txt
复制
HttpResponse httpResponse = HttpRequest.post("https://ip:port/senddata")
代码语言:txt
复制
                .contentType("text/plain")
代码语言:txt
复制
                .body("requestBody")
代码语言:txt
复制
                .execute();
代码语言:txt
复制
System.out.println(httpResponse.getStatus());
代码语言:txt
复制
System.out.println(httpResponse.body());

只要把原来的http替换成https就行,非常的方便!

3、双向认证配置和客户端调用

3.1、Nginx配置

也是在http{...}配置块中新增一个server配置块。

代码语言:txt
复制
# 负载均衡配置
代码语言:txt
复制
upstream backend {
代码语言:txt
复制
    server 192.168.0.1:10900;
代码语言:txt
复制
    server 192.168.0.2:10900;
代码语言:txt
复制
    server 192.168.0.3:10900;
代码语言:txt
复制
}
代码语言:txt
复制
server {
代码语言:txt
复制
    listen       21000 ssl;
代码语言:txt
复制
    server_name  a.com;
代码语言:txt
复制
    ssl_certificate      ../ssl/server.crt;  # server公钥证书
代码语言:txt
复制
    ssl_certificate_key  ../ssl/server.key;  # server私钥
代码语言:txt
复制
    ssl_client_certificate ../clientcrt/clientA.crt;  # client公钥证书
代码语言:txt
复制
    ssl_verify_client on;  # 开启客户端证书验证
代码语言:txt
复制
    ssl_session_cache    shared:SSL:1m;
代码语言:txt
复制
    ssl_session_timeout  5m;
代码语言:txt
复制
    ssl_ciphers  HIGH:!aNULL:!MD5;
代码语言:txt
复制
    ssl_prefer_server_ciphers  on;
代码语言:txt
复制
    access_log logs/a_access.log;
代码语言:txt
复制
    error_log logs/a_error.log;
代码语言:txt
复制
    location / {
代码语言:txt
复制
        proxy_pass    http://backend;
代码语言:txt
复制
    }
代码语言:txt
复制
}

因为是双向认证,不仅客户端要认证服务端,服务端也需要认证客户端,所以相较于单向认证,会多出以下2个配置参数:

  • ssl_verify_client on 表示开启双向认证,服务端也要认证客户端,该参数默认是off关闭。
  • ssl_client_certificate 配置客户端公钥证书存放的路径位置。
3.2、Postman调用
  1. 在设置General中先把SSL certificate verification关掉。
  2. 然后在Certificates中配置客户端公私钥证书。注意这里的地址和端口要与实际的一致,否则请求时会认证失败。
  3. 或者可以只配置p12文件,同时也要配置p12文件的密码。p12文件可以认为是一对公私钥的合体文件,通常会有密码保护;可以通过openssl命令生成(将公私钥两个文件合成得到一个p12文件)。
  4. 最后发起请求
3.3、浏览器调用

浏览器一般用单向认证会比较多,双向认证的详细配置步骤这里就不多啰嗦了。主要就是把自己客户端的p12文件导入到自己电脑的证书列表中再访问服务端,如果提示服务端的证书有风险,点击继续就行。

3.4、Java客户端调用

这里我们使用httpclient来发起https的请求进行双向认证。

代码语言:txt
复制
<dependency>
代码语言:txt
复制
    <groupId>org.apache.httpcomponents</groupId>
代码语言:txt
复制
    <artifactId>httpclient</artifactId>
代码语言:txt
复制
    <version>4.5.8</version>
代码语言:txt
复制
</dependency>

不过也分为2种方式:

  1. 一种是要把服务端公钥证书导入到客户端JDKcacerts文件中;
  2. 另一种则是把服务端的公钥证书自行生成一个truststore信任库,由客户端程序读取这个信任库然后发起https请求进行双向认证。
3.4.1、导入cacerts进行访问
代码语言:txt
复制
# 切换到jdk的security目录
代码语言:txt
复制
cd $JAVA_HOME/jre/lib/security
代码语言:txt
复制
# 将服务端证书导入cacerts文件中,指定别名myserver,-file参数指定的就是服务端公钥证书的路径
代码语言:txt
复制
keytool -import -alias myserver -keystore cacerts -storepass changeit -file C:/Users/my/Desktop/cert/server_ssl/server.crt
代码语言:txt
复制
# 查看所有证书
代码语言:txt
复制
keytool -list -keystore cacerts -storepass changeit
代码语言:txt
复制
# 删除指定别名的证书
代码语言:txt
复制
keytool -delete -alias myserver -keystore cacerts -storepass changeit

示例代码

代码语言:txt
复制
import org.apache.http.HttpEntity;
代码语言:txt
复制
import org.apache.http.client.config.RequestConfig;
代码语言:txt
复制
import org.apache.http.client.methods.CloseableHttpResponse;
代码语言:txt
复制
import org.apache.http.client.methods.HttpPost;
代码语言:txt
复制
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
代码语言:txt
复制
import org.apache.http.entity.ContentType;
代码语言:txt
复制
import org.apache.http.entity.StringEntity;
代码语言:txt
复制
import org.apache.http.impl.client.CloseableHttpClient;
代码语言:txt
复制
import org.apache.http.impl.client.HttpClients;
代码语言:txt
复制
import org.apache.http.ssl.SSLContexts;
代码语言:txt
复制
import org.apache.http.util.EntityUtils;
代码语言:txt
复制
import org.junit.Test;
代码语言:txt
复制
import javax.net.ssl.SSLContext;
代码语言:txt
复制
import java.io.File;
代码语言:txt
复制
import java.io.FileInputStream;
代码语言:txt
复制
import java.io.InputStream;
代码语言:txt
复制
import java.security.KeyStore;
代码语言:txt
复制
public class SSLTestCase {
代码语言:txt
复制
    /**
代码语言:txt
复制
     * 客户端p12证书路径
     */
    private String pfxPath = "C:/Users/my/Desktop/cert/client.p12";
    /**
     * 客户端p12证书密码
     */
    private String pfxPasswd = "123456";
代码语言:txt
复制
    private String url = "https://139.9.127.172:21000/senddata";
代码语言:txt
复制
    @Test
代码语言:txt
复制
    public void SSLTestCase() throws Exception {
代码语言:txt
复制
        /**
代码语言:txt
复制
         * 1.加载P12证书
         */
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        InputStream instream = new FileInputStream(new File(pfxPath));
        try {
            keyStore.load(instream, pfxPasswd.toCharArray());
        } finally {
            instream.close();
        }
        SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, pfxPasswd.toCharArray()).build();
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext,
                new String[]{"TLSv1"}, // supportedProtocols ,这里可以根据实际情况设置
                null,
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
        /**
         * 2.使用httpclient4.5.8发送post请求
         */
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        // 设置超时时间
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(30000).build();
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
//            httpPost.addHeader("Connection", "keep-alive"); // 设置一些header等
            String requestBody = "requestBody";
//            StringEntity stringEntity = new StringEntity(requestBody, "UTF-8");
            StringEntity stringEntity = new StringEntity(requestBody, ContentType.create("text/plain", "UTF-8"));
            httpPost.setEntity(stringEntity);
            response = httpclient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String respBody = EntityUtils.toString(response.getEntity(), "UTF-8");
            EntityUtils.consume(entity);
            System.out.println(respBody);
        } finally {
            if (httpclient != null) {
                httpclient.close();
            }
            if (response != null) {
                response.close();
            }
        }
    }
}
3.4.2、自行生成truststore信任库文件进行访问

如果服务器的JDK/JRE不能随便改动,我们还可以自行生成truststore信任库,由程序来读取这个信任库中的证书。

代码语言:txt
复制
# -keystore参数指定生成后的truststore文件,-file参数指定服务公钥证书路径
代码语言:txt
复制
keytool -keystore C:/Users/my/Desktop/cert/server_ssl/server.truststore -keypass 654321 -storepass 654321 -alias myservertruststore -import -trustcacerts -file C:/Users/my/Desktop/cert/server_ssl/server.crt

示例代码

代码语言:txt
复制
import org.apache.http.HttpEntity;
代码语言:txt
复制
import org.apache.http.client.config.RequestConfig;
代码语言:txt
复制
import org.apache.http.client.methods.CloseableHttpResponse;
代码语言:txt
复制
import org.apache.http.client.methods.HttpPost;
代码语言:txt
复制
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
代码语言:txt
复制
import org.apache.http.entity.ContentType;
代码语言:txt
复制
import org.apache.http.entity.StringEntity;
代码语言:txt
复制
import org.apache.http.impl.client.CloseableHttpClient;
代码语言:txt
复制
import org.apache.http.impl.client.HttpClients;
代码语言:txt
复制
import org.apache.http.util.EntityUtils;
代码语言:txt
复制
import org.junit.Test;
代码语言:txt
复制
import javax.net.ssl.KeyManagerFactory;
代码语言:txt
复制
import javax.net.ssl.SSLContext;
代码语言:txt
复制
import javax.net.ssl.TrustManagerFactory;
代码语言:txt
复制
import java.io.File;
代码语言:txt
复制
import java.io.FileInputStream;
代码语言:txt
复制
import java.io.InputStream;
代码语言:txt
复制
import java.security.KeyStore;
代码语言:txt
复制
import java.security.SecureRandom;
代码语言:txt
复制
public class SSLTestCase2 {
代码语言:txt
复制
    /**
代码语言:txt
复制
     * 客户端p12证书路径
     */
    private String pfxPath = "C:/Users/my/Desktop/cert/client.p12";
    /**
     * 客户端p12证书密码
     */
    private String pfxPasswd = "123456";
    /**
     * 信任库路径
     */
    private String trustStroreFile = "C:/Users/my/Desktop/cert/server_ssl/server.truststore";
    /**
     * 信任库密码
     */
    private String trustStorePwd = "654321";
代码语言:txt
复制
    private String url = "https://139.9.127.172:21000/senddata";
代码语言:txt
复制
    @Test
代码语言:txt
复制
    public void SSLTestCase2() throws Exception {
代码语言:txt
复制
        /**
代码语言:txt
复制
         * 1.初始化密钥库
         */
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
        KeyStore keyStore = getKeyStore(pfxPath, pfxPasswd, "PKCS12");
        keyManagerFactory.init(keyStore, pfxPasswd.toCharArray());
代码语言:txt
复制
        /**
代码语言:txt
复制
         * 2.初始化信任库
         */
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
        KeyStore trustkeyStore = getKeyStore(trustStroreFile, trustStorePwd, "JKS");
        trustManagerFactory.init(trustkeyStore);
代码语言:txt
复制
        SSLContext sslContext = SSLContext.getInstance("SSL");
代码语言:txt
复制
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
代码语言:txt
复制
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,
代码语言:txt
复制
                new String[]{"TLSv1"}, // supportedProtocols ,这里可以根据实际情况设置
代码语言:txt
复制
                null,
代码语言:txt
复制
                SSLConnectionSocketFactory.getDefaultHostnameVerifier());
代码语言:txt
复制
        /**
代码语言:txt
复制
         * 3.使用httpclient4.5.8发送post请求
         */
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
        // 设置超时时间
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(5000)
                .setSocketTimeout(30000).build();
        CloseableHttpResponse response = null;
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setConfig(requestConfig);
//            httpPost.addHeader("Connection", "keep-alive"); // 设置一些header等
            String requestBody = "requestBody";
//            StringEntity stringEntity = new StringEntity(requestBody, "UTF-8");
            StringEntity stringEntity = new StringEntity(requestBody, ContentType.create("text/plain", "UTF-8"));
            httpPost.setEntity(stringEntity);
            response = httpclient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String respBody = EntityUtils.toString(response.getEntity(), "UTF-8");
            EntityUtils.consume(entity);
            System.out.println(respBody);
        } finally {
            if (httpclient != null) {
                httpclient.close();
            }
            if (response != null) {
                response.close();
            }
        }
    }
代码语言:txt
复制
    private KeyStore getKeyStore(String pfxPath, String pfxPasswd, String type) throws Exception {
代码语言:txt
复制
        KeyStore keyStore = KeyStore.getInstance(type);
代码语言:txt
复制
        InputStream instream = new FileInputStream(new File(pfxPath));
代码语言:txt
复制
        try {
代码语言:txt
复制
            keyStore.load(instream, pfxPasswd.toCharArray());
代码语言:txt
复制
        } finally {
代码语言:txt
复制
            instream.close();
代码语言:txt
复制
        }
代码语言:txt
复制
        return keyStore;
代码语言:txt
复制
    }
代码语言:txt
复制
}

总结双向认证的几种客户端调用方式,可以发现只有Java客户端调用时会需要用到服务端证书;而用Postman、浏览器这些客户端工具时我们并没有配置服务端证书,是因为在一开始建立连接时,服务端本来就会把自己的证书发给客户端去进行认证。

3.5、客户端获取服务端公钥证书

有时候,产线环境的服务端公钥证书并不能很方便地拿出来给客户端去使用,这时候需要客户端通过执行openssl的一个命令来获取服务端的公钥证书,当然前提是Nginx服务需要启动。

代码语言:txt
复制
openssl s_client -connect 139.9.127.172:21000 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >./server.crt
  • -connect:Nginx服务器的ip和端口。
  • 服务端公钥证书最后输出到客户端本地目录的server.crt文件。

4、双向认证接入多个客户端

很多时候作为服务端要对接多个客户端,每个客户端都有自己的证书,Nginx服务端需要为每一个接入的客户端渠道配置一个server块来进行双向认证。既然是多个server配置块,那就会涉及到对接入的客户端匹配哪个server块来进行双向认证的问题。

首先Nginx会根据不同的监听端口来匹配,但是这样会为每个接入的客户端渠道新开放一个端口。如何统一用一个监听端口接入所有客户端的https请求并验证各个渠道的证书合法性,主要有以下2种方式。

4.1、SNI 多域名匹配不同证书

这里就需要使用到SNI功能。如果编译Nginx开启了http_ssl_module模块,一般默认也是启用SNI功能的,可以通过`./nginx

-V`命令查看。

Nginx配置多个vhost

代码语言:txt
复制
# 负载均衡配置
代码语言:txt
复制
upstream backend {
代码语言:txt
复制
    server 192.168.0.1:10900;
代码语言:txt
复制
    server 192.168.0.2:10900;
代码语言:txt
复制
    server 192.168.0.3:10900;
代码语言:txt
复制
}
代码语言:txt
复制
# 渠道A
代码语言:txt
复制
server {
代码语言:txt
复制
    listen       21000 ssl;
代码语言:txt
复制
    server_name  a.test.com;
代码语言:txt
复制
    ssl_certificate      ../ssl/server.crt;  # server公钥证书
代码语言:txt
复制
    ssl_certificate_key  ../ssl/server.key;  # server私钥
代码语言:txt
复制
    ssl_client_certificate ../clientcrt/clientA.crt;  # client公钥证书
代码语言:txt
复制
    ssl_verify_client on;  # 开启客户端证书验证
代码语言:txt
复制
    ssl_session_cache    shared:SSL:1m;
代码语言:txt
复制
    ssl_session_timeout  5m;
代码语言:txt
复制
    ssl_ciphers  HIGH:!aNULL:!MD5;
代码语言:txt
复制
    ssl_prefer_server_ciphers  on;
代码语言:txt
复制
    access_log logs/a_access.log;
代码语言:txt
复制
    error_log logs/a_error.log;
代码语言:txt
复制
    location / {
代码语言:txt
复制
        proxy_pass    http://backend;
代码语言:txt
复制
    }
代码语言:txt
复制
}
代码语言:txt
复制
# 渠道B
代码语言:txt
复制
server {
代码语言:txt
复制
    listen       21000 ssl;
代码语言:txt
复制
    server_name  b.test.com;
代码语言:txt
复制
    ssl_certificate      ../ssl/server.crt;  # server公钥证书
代码语言:txt
复制
    ssl_certificate_key  ../ssl/server.key;  # server私钥
代码语言:txt
复制
    ssl_client_certificate ../clientcrt/clientB.crt;  # client公钥证书
代码语言:txt
复制
    ssl_verify_client on;  # 开启客户端证书验证
代码语言:txt
复制
    ssl_session_cache    shared:SSL:1m;
代码语言:txt
复制
    ssl_session_timeout  5m;
代码语言:txt
复制
    ssl_ciphers  HIGH:!aNULL:!MD5;
代码语言:txt
复制
    ssl_prefer_server_ciphers  on;
代码语言:txt
复制
    access_log logs/b_access.log;
代码语言:txt
复制
    error_log logs/b_error.log;
代码语言:txt
复制
    location / {
代码语言:txt
复制
        proxy_pass    http://backend;
代码语言:txt
复制
    }
代码语言:txt
复制
}

配置的listen端口是一样的,但每个vhostserver_name不同,这里其实是通过配置不同的server_name来匹配各个不同的客户端,需要客户端请求的url中的域名(Http请求头中的Host字段值)与配置的server_name一致。比如:

客户端的域名解析可以通过域名解析服务器或者可以在本地hosts文件中配置。

**需要注意的是:如果使用SNI功能,服务器签发公钥证书时,填写的域名信息`Common

Name需要是泛域名,比如*.test.com`。这样客户端在验证服务器域名时才会通过**。

另外,Nginx在同一个端口下匹配多个vhost时,如果找不到匹配的server_name,则会使用默认的vhost(默认第一个)来进行认证。为了防止隐式匹配带来的一些问题困扰,可以通过default_server显式地指定一个默认的vhost,一律返回401。

代码语言:txt
复制
server {
代码语言:txt
复制
    listen 21000 ssl default_server;
代码语言:txt
复制
    ssl_certificate      ../ssl/server.crt;  # server公钥证书
代码语言:txt
复制
    ssl_certificate_key  ../ssl/server.key;  # server私钥
代码语言:txt
复制
    access_log logs/default_access.log;
代码语言:txt
复制
    error_log logs/default_error.log;
代码语言:txt
复制
    location / {
代码语言:txt
复制
        return 401;
代码语言:txt
复制
    }
代码语言:txt
复制
}
4.2、CA根证书统一签发客户端证书

先统一生成CA根证书,然后由根证书派生出服务端和各个客户端的证书。

CA

生成root根证书

代码语言:txt
复制
# 创建根证书私钥
代码语言:txt
复制
openssl genrsa -out root.key 1024
代码语言:txt
复制
# 创建根证书请求文件
代码语言:txt
复制
openssl req -new -key root.key -out root.csr
代码语言:txt
复制
# 创建根证书
代码语言:txt
复制
openssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650

生成服务端证书

代码语言:txt
复制
# 生成服务器端证书私钥
代码语言:txt
复制
openssl genrsa -out server.key 1024
代码语言:txt
复制
# 生成服务器证书请求文件
代码语言:txt
复制
openssl req -new -key server.key -out server.csr
代码语言:txt
复制
# 生成服务器端公钥证书
代码语言:txt
复制
openssl x509 -req -in server.csr -out server.crt -signkey server.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650

生成客户端证书

代码语言:txt
复制
# 生成客户端证书私钥
代码语言:txt
复制
openssl genrsa -out client.key 1024
代码语言:txt
复制
# 生成客户端证书请求文件
代码语言:txt
复制
openssl req -new -key client.key -out client.csr
代码语言:txt
复制
# 生成客户端证书
代码语言:txt
复制
openssl x509 -req -in client.csr -out client.crt -signkey client.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
代码语言:txt
复制
# 生客户端p12格式证书,需要输入一个密码,选一个好记的,比如123456
代码语言:txt
复制
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12

一定要注意的是,根证书的域名信息Common Name这个字段和客户端证书、服务器端证书不能一样

然后在Nginxssl_client_certificate字段配置根证书的路径,这样就可以验证所有它颁发的客户端证书。不需要再为每个客户端渠道创建一个server配置块去认证。

代码语言:txt
复制
# 根证书统一认证
代码语言:txt
复制
server {
代码语言:txt
复制
    listen       21000 ssl;
代码语言:txt
复制
    server_name  localhost;
代码语言:txt
复制
    ssl_certificate      ../ssl/server.crt;  # server公钥证书
代码语言:txt
复制
    ssl_certificate_key  ../ssl/server.key;  # server私钥
代码语言:txt
复制
    ssl_client_certificate ../ssl/root.crt;  # 根证书,可以验证所有它颁发的客户端证书
代码语言:txt
复制
    ssl_verify_client on;  # 开启客户端证书验证
代码语言:txt
复制
    ssl_session_cache    shared:SSL:1m;
代码语言:txt
复制
    ssl_session_timeout  5m;
代码语言:txt
复制
    ssl_ciphers  HIGH:!aNULL:!MD5;
代码语言:txt
复制
    ssl_prefer_server_ciphers  on;
代码语言:txt
复制
    access_log logs/access.log;
代码语言:txt
复制
    error_log logs/error.log;
代码语言:txt
复制
    location / {
代码语言:txt
复制
        proxy_pass    http://192.168.0.1:10900;
代码语言:txt
复制
    }
代码语言:txt
复制
}

参考链接

附录

keytool相关命令

keytool生成证书

代码语言:txt
复制
# 生成服务端jks
代码语言:txt
复制
keytool -genkey -alias servertest -keysize 2048 -validity 3650 -keyalg RSA -dname "CN=client.test.com, OU=R & D department, O=\"BJ SOS Software Tech Co., Ltd\", L=Beijing, S=Beijing, C=CN" -keypass 123456 -storepass 123456 -keystore server.jks
代码语言:txt
复制
# -storepass 指定密钥库的密码(获取keystore信息所需的密码) 
代码语言:txt
复制
# -keypass 指定别名条目的密码(私钥的密码) 
代码语言:txt
复制
# keystore信息的查看
代码语言:txt
复制
keytool -list -v -keystore server.jks -storepass 123456
代码语言:txt
复制
# 从jks中导出公钥证书
代码语言:txt
复制
keytool -export -alias servertest -keystore server.jks -storepass 123456 -file server.crt
代码语言:txt
复制
# 查看导出的证书信息
代码语言:txt
复制
keytool -printcert -file server.crt
代码语言:txt
复制
# 生成客户端jks
代码语言:txt
复制
keytool -genkey -alias clienttest -keysize 2048 -validity 3650 -keyalg RSA -dname "CN=client.test.com, OU=R & D department, O=\"BJ SOS Software Tech Co., Ltd\", L=Beijing, S=Beijing, C=CN" -keypass 123456 -storepass 123456 -keystore client.jks
代码语言:txt
复制
# 将服务端公钥证书导入的客户端jks信任库中
代码语言:txt
复制
keytool -import -trustcacerts -alias servertest -file server.crt -storepass 123456 -keystore client.jks
代码语言:txt
复制
# keystore信息的查看
代码语言:txt
复制
keytool -list -v -keystore client.jks -storepass 123456

openssl 2 keytool

代码语言:txt
复制
# 生成私钥
代码语言:txt
复制
openssl genrsa -out client.key 1024
代码语言:txt
复制
# 生成公钥证书
代码语言:txt
复制
openssl req -new -x509 -key client.key -out client.crt -days 3650
代码语言:txt
复制
# 合成p12文件
代码语言:txt
复制
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12 -passout pass:123456 -name clienttest
代码语言:txt
复制
# p12文件转jks
代码语言:txt
复制
keytool -importkeystore -srcstoretype PKCS12 -srckeystore client.p12 -srcstorepass 123456 -srcalias clienttest -deststoretype JKS -destalias clienttest -deststorepass 123456 -destkeypass 123456 -destkeystore client.jks

keytool 2 openssl

代码语言:txt
复制
# 生成jks
代码语言:txt
复制
keytool -genkey -alias clienttest -keysize 2048 -validity 3650 -keyalg RSA -dname "CN=client.test.com, OU=R & D department, O=\"BJ SOS Software Tech Co., Ltd\", L=Beijing, S=Beijing, C=CN" -keypass 123456 -storepass 123456 -keystore client.jks
代码语言:txt
复制
# 从jks中导出公钥证书
代码语言:txt
复制
keytool -export -alias clienttest -keystore client.jks -storepass 123456 -file client.crt
代码语言:txt
复制
# jks转p12文件
代码语言:txt
复制
keytool -importkeystore -srcstoretype JKS -srckeystore client.jks -srcstorepass 123456 -srcalias clienttest -srckeypass 123456 -deststoretype PKCS12 -destkeystore client.p12 -deststorepass 123456 -destalias clienttest -destkeypass 123456 -noprompt
代码语言:txt
复制
# p12文件转pem格式
代码语言:txt
复制
openssl pkcs12 -in client.p12 -out client.pem.p12 -passin pass:123456 -passout pass:123456
代码语言:txt
复制
# 从pem格式文件单独输出私钥
代码语言:txt
复制
openssl rsa -in client.pem.p12 -passin pass:123456 -out client.key -passout pass:123456

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、环境准备
    • 1.1、Nginx
      • 1.2、openssl生成公私钥
      • 2、单向认证配置和客户端调用
        • 2.1、Nginx配置
          • 2.2、Postman调用
            • 2.3、浏览器调用
              • 2.4、Java客户端调用
              • 3、双向认证配置和客户端调用
                • 3.1、Nginx配置
                  • 3.2、Postman调用
                    • 3.3、浏览器调用
                      • 3.4、Java客户端调用
                        • 3.5、客户端获取服务端公钥证书
                        • 4、双向认证接入多个客户端
                          • 4.1、SNI 多域名匹配不同证书
                            • 4.2、CA根证书统一签发客户端证书
                            • 参考链接
                            • 附录
                              • keytool相关命令
                              相关产品与服务
                              SSL 证书
                              腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档