关于JAVA与nginx双向认证的总结
目录
一、 研究目的和简要
二、 Openssl生成级联证书
三、 Nginx配置双向认证
四、 Java实现双向认证
五、 证书关系图
。
一、 研究目的和简要
看着简单的SSL证书,可以说绝对被SSL证书的外表欺骗了。为什么写这么多?我也很讨厌要看一大堆东西才实现的功能!但SSL双向认证真需要这么多字来说明(虽然废话也挺多,例如这句)。笔者研究了1个星期,最终是实现了双向认证,但对ssl认证过程和加密过程还是没有完全明白。网上是有很多关于生成证书和java代码实例,但大多都不能在实际工程场景中发挥作用。内容是挺多的,请耐心看,虽然理解方面存在我的脑洞,但可以保证这个文章绝对不是从网上简单的复制粘贴,是真材实料,杠杠的研究。
目的:
熟悉https请求双向认证的原理和运用方法,并在实际环境中得到正确使用
简要:
由于程序研究过程以及网络培训视频、网络文章等多以自己生成非正规CA发布的证书,绝大部分生成的证书与实际服务器使用的证书差别较大,在笔者生成大量证书(其实也就10套左右)后,感谢地址
https://blog.csdn.net/langeldep/article/details/54675898?utm_source=blogxgwz8
生成的证书经笔者测试可实现双向验证。
测试包括(细节将在“Nginx配置双向认证”和“Java实现双向认证”中体现):同一套证书验证可通过验证,客户端不信任或者信任另一套证书不可通过验证等。
证书生成后,nginx需要正确配置和Java代码正确配置才可达到双向认证成功的效果。Nginx配置错误或者无效,多出现无论以什么证书,只要有证书就可通过验证的情况。在nginx配置正确后,这时Java代码只要没有正确配置就会出现验证失败。
以上是关于“Openssl生成级联证书”、“Nginx配置双向认证”、“Java实现双向认证”等小节之必要的说明。
二、 Openssl生成级联证书
参考来源: https://blog.csdn.net/langeldep/article/details/54675898?utm_source=blogxgwz8
以下所有步骤都是在root用户下执行。
第一步:生成相关目录和文件
笔者使用的是linux虚拟机研究,openssl安装后与参考网站的一样,配置文件在/etc/pki/tls,所以跟着作了:
mkdir /etc/pki/ca_linvo
cd /etc/pki/ca_linvo
mkdir root server client newcerts
echo 01 > serial
echo 01 > crlnumber
touch index.txt
经过思考,生成这些文件可能是为了区分原文件夹,避免证书管理混乱。而笔者在生成第二套证书时,发现用原来的文件夹保存第二套也是有问题的,这涉及到第二步为什么要修改openssl配置文件。所以还是乖乖生成这些文件夹和文件,每新建一套都创建一个。
第二步:修改openssl配置
修改配置之前,笔者担忧出错,所以拷贝了一份配置文件的备份,参考操作为:
cp /etc/pki/tls/openssl.cnf /etc/pki/tls/openssl.cnf_bak
vi /etc/pki/tls/openssl.cnf
说明一下,上面的openssl.cnf就是openssl的配置文件
[ ca ]下的default_ca改为自己的ca名字,例如参考网站中的[ CA_linvo ]
操作方式:注释掉原来的,添加自己的
复制ca设置并改成自己的。原设置是[ CA_default ],赋值一遍粘贴一下就可以了
将自己复制的那一份设置中的下面三个参数改为对应参数,具体的参数根据创建的文件夹而定:
dir = /etc/pki/ca_linvo
certificate = $dir/root/ca.crt
private_key = $dir/root/ca.key
如果只是创建一套,可以忽略这一段话→→→这里certificatec和private_key的参数说明了根证书只能是ca.crt,根私钥只能是ca.key,所以笔者在创建第二套级联证书的根证书时,想到要去修改配置文件怕会造成证书管理混乱,所以在生成第二套时,又创建了一个证书文件夹ca_lan并在配置文件中加入了另一个CA_lan的设置,这样两套证书就分开管理了。
修改完后,shift+ZZ或者:wq保存修改
(tips: vi或者vim在按入i键后进入编辑模式,编辑模式中按Esc退出编辑模式,在这种情况下使用shift+ZZ或者:wq才是退出并保存修改)
第三步:创建根证书
创建根证书私钥:
openssl genrsa -des3 -out /etc/pki/ca_linvo/root/ca.key 2048
创建根证书请求文件:
openssl req -new -key /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/root/ca.csr
创建根证书:
openssl x509 -req -days 3650 -in /etc/pki/ca_linvo/root/ca.csr -signkey /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/root/ca.crt
创建证书吊销列表:
openssl ca -gencrl -out /etc/pki/ca_linvo/root/ca.crl -crldays 7
需要注意这里的路径需要改为自己创建的文件夹路径,例如笔者创建第二套证书时,创建秘钥是使用:openssl genrsa -des3 -out /etc/pki/ca_lan/root/ca.key 2048,不同点是-out后面的路径不同。
ca.key为私钥,csr是请求文件,crt是证书,crl证书吊销列表。
笔者理解如下:
最终目的是得到crt证书。形象地讲,有了私钥是一种身份证明,就像我是有钥匙的人了,我用这个钥匙可以去锁东西了,想打开我锁的东西,那就请得到我允许打开的证明(crt证书),证书怎么来?把我承认的请求(csr)发给我,我就给你证书。这个想象式理解只是笔者自己的脑洞,可能跟实际作用有出入。
可以说笔者并没有深入研究,这里大部分是笔者脑洞,包括所有小节。
第四步:生成服务器证书
生成服务器证书私钥:
openssl genrsa -des3 -out /etc/pki/ca_linvo/server/server.key 2048
生成服务器证书请求文件:
openssl req -new -key /etc/pki/ca_linvo/server/server.key -out /etc/pki/ca_linvo/server/server.csr
生成服务器证书:
openssl ca -in /etc/pki/ca_linvo/server/server.csr -cert /etc/pki/ca_linvo/root/ca.crt -keyfile /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/server/server.crt -days 3650
同样要注意修改一下路径未自己创建的文件夹路径。
同样脑洞开始:证书比根证书多了一个根证书的签名。就像这个证书虽然是一个私钥生成的,但这个有证书还被那个人的老板签了个字,这样它老板可以解释这个证书是不是被认可的。客户端拿着这个证书用根证书验证就可以通过。
第五步:生成客户端证书
生成客户端证书私钥:
openssl genrsa -des3 -out /etc/pki/ca_linvo/client/client.key 2048
生成客户端证书请求文件:
openssl req -new -key /etc/pki/ca_linvo/client/client.key -out /etc/pki/ca_linvo/client/client.csr
生成客户端证书:
openssl ca -in /etc/pki/ca_linvo/client/client.csr -cert /etc/pki/ca_linvo/root/ca.crt -keyfile /etc/pki/ca_linvo/root/ca.key -out /etc/pki/ca_linvo/client/client.crt -days 3650
同样要注意修改一下路径未自己创建的文件夹路径。
方式跟第四步服务器证书生成一样。参考网站中提到服务器似乎不用根证书参与,所以有以下脑洞:
浏览器访问会从服务器中获取服务器证书,所以浏览器只添加客户端证书就可以。
Tips:其它有帮助的命令,以下命令自己注意一下路径和文件名
生成客户端证书安装pfx文件,双击就可安装到电脑中,浏览器就可识别:
openssl pkcs12 -export -inkey /etc/pki/ca_linvo/client/client.key -in /etc/pki/ca_linvo/client/client.crt -out /etc/pki/ca_linvo/client/client.pfx
生成java可用的p12文件:
openssl pkcs12 -export -clcerts -in client-cert.cer -inkey client-key.key -out client.p12
秘钥转非秘秘钥:可以用来转服务器证书,转了非秘秘钥后,nginx重启就不用输入秘钥密码
openssl rsa -in danfeng.key -out danfeng_nopass.key
三、 Nginx配置双向认证
以下内容在nginx配置文件中设置,在主体中设置。
server{
}
其它设置自行去学习,这里只列出配置部分的设置
server{
…
ssl on;
ssl_certificate /xxxxxxxxxxxxxx/ssl/myssl/server.crt;
ssl_certificate_key /xxxxxxxxxxxxxx/ssl/myssl/server_nopass.key;
ssl_client_certificate /xxxxxxxxxxxxxx/ssl/myssl/ca.crt;
ssl_verify_depth 2;
ssl_verify_client on;
ssl_session_timeout 5m;
ssl_protocols SSLv2 SSLv3 TLSv1;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
…
}
主要由以下几个参数需要理解:
ssl on : 设置启动https请求
ssl_certificate :设置服务器证书
ssl_certificate_key :设置服务器证书秘钥
ssl_client_certificate :设置根证书,用于验证客户端
ssl_verify_depth :设置验证程度
ssl_verify_client :设置是否开启客户端验证
其它的为什么不写出来?因为我也没有去详细研究
测试过程:
客户端带证书访问可正常访问
客户端不带证书不可访问
注释掉ssl_verify_client那一行,不管有没有客户端证书都可以访问,并且同时注释掉ca证书那一行也一样,而待客户端证书访问的响应数据也是正常。这里怀疑客户端发送内容是不是没有加密?
四、 Java实现双向认证
java需要一个信任服务器证书和客户端证书,下面用两种方式处理证书和信任库(应该针对不同的证书有不同的方式)。
第一种方式:使用httpclient处理:
java代码:参考来源:https://www.cnblogs.com/dreamingodd/p/7491098.html
import org.apache.http.HttpEntity;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
/**
* #1
* HTTPS 双向认证 - direct into cacerts
* @Author Ye_Wenda
* @Date 7/11/2017
*/
public class HttpsDemo {
// 客户端证书路径,用了本地绝对路径,需要修改
private final static String TRUST_PATH = "F:\\自己生成的证书\\openssl证书\\级联证书\\ca_linvo\\server\\tserver.keystore";
private final static String PFX_PATH = "F:\\自己生成的证书\\openssl证书\\级联证书\\ca_linvo\\client\\client.p12";
private final static String PFX_PWD = "333333"; //客户端证书密码及密钥库密码
public static String sslRequestGet(String url) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream instream = new FileInputStream(new File(PFX_PATH));
try {
// 这里就指的是KeyStore库的密码
keyStore.load(instream, PFX_PWD.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, PFX_PWD.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext
, new String[] {
"TLSv1" } // supportedProtocols ,这里可以按需要设置
, null // supportedCipherSuites
, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
// HttpGet httpget = new HttpGet(url);
HttpPost httpget = new HttpPost(url);
// httpost.addHeader("Connection", "keep-alive");// 设置一些heander等
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity