一、基础概念
注:以下概念除专业名词外,均为个人理解,不具备权威性。
1、什么是系统安全管理
置于公网的系统,通常都需要一定的安全管理,据我个人理解,这里的安全管理主要分三个方面:
一是应用内的权限控制,比如具体应用的用户名、密码等;
二是应用数据传输过程中的安全机制,例如各种报文的加解密方案;
三是数据传输前的通讯安全机制,保证通讯双方都是可靠可信任的,PKI就是其中一个解决方案。
2、什么是PKI
PKI是 Public Key Infrastructure的简称,意思是公钥基础设施。
公钥基础设施是提供公钥加密和数字签名服务的系统或平台,目的是为了管理密钥和证书。通过证书和秘钥来确认通讯双方是否可信任。
3、什么是CA
CA是Certificate Authority的简称,即证书的签发机构,它是PKI的核心。
正常情况来说,CA是具有权威性的机构,通过CA获取证书需要给钱。
但是有的时候可能不想用CA机构的证书,又想要使用https站点,那么可能就需要自己生成证书,但是这种证书浏览器是认为不安全的,本文档后边的具体步骤即针对这种场景。
4、什么是https
https是http ssl,通俗点说,就是采用http通讯的安全传输协议,用来保证http传输过程中数据的机密性、完整性和可靠性,ssl需要证书。
5、单向认证
网络通讯是双向的,但是安全认证不一定都是双向。大多数情况下可能都是单向的,只需要客户端确认服务端是可靠的,而服务端不管客户端是否可靠。即客户端,比如浏览器会验证服务端证书,服务端不需要客户端证书。
6、双向认证
双向认证相对于单向认证,即客户端需要确认服务端是否可信,服务端也需要确认客户端是否可信。双方都要验证对方的证书。
二、实现步骤
1、环境
本次技术调研过程全程都在自己的电脑上,采用物理机加虚拟机的方式:
物理机:win8 ie
虚拟机:redhat6.4 nginx tomcat openssl
2、nginx安装
2.1、安装包准备
nginx安装需要依赖其他的一些组件,网上说有以下三个必要依赖:
openssl
pcre
zlib
但实际安装过程发现只有openssl是必要的,其他两个可以排除,因此实际安装时下载了两个安装包:
nginx-1.12.2.tar.gz
openssl-1.0.0a.tar.gz
2.2、安装openssl
2.2.1、解压
tar -zxvf openssl-1.0.0a.tar.gz
2.2.2、配置
进入解压后目录
./configure
2.2.3、编译
make
2.2.4、安装
make install
2.3、安装nginx
2.3.1、解压
tar -zxvf nginx-1.12.2.tar.gz
2.3.2、配置
进入解压后的文件夹配置,这里需要注意的是,使用参数排除了pcre和zlib,同时指定了openssl的安装目录,并指定安装ssl模块
./configure --without-http_rewrite_module --without-http_gzip_module --with-http_ssl_module --with-openssl=/home/tuzongxun/openssl/openssl-1.0.0a
2.3.3、编译
make
2.3.4、安装
make install
安装后会看到/usr/local目录中多了一个nginx目录,即nginx的安装目录
3、证书生成
3.1、生成根证书
mkdir ssl 创建证书存放的目录
cd ssl 进入证书存放目录
openssl genrsa -out ca.key 2048 生成根证书私钥
制作根证书
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
3.2、生成服务端证书
先生成服务端私钥
openssl genrsa -out server.pem 1024
openssl rsa -in server.pem -out server.key
生成签发请求,注意这里的common name必须是需要访问的域名,其他的内容可以和根证书填写的一样
根据签发请求和服务端私钥生成服务端证书
openssl x509 -req -sha256 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out server.crt
4、单向认证
CA单向认证,只需要验证服务端是否可信即可,因此也就只需要上边的步骤生成了根证书和服务端证书即可,然后就可以配置nginx了。
4.1、nginx配置
进入nginx的安装目录找到nginx.conf文件更改配置
主要是更改server里的内容,更改后如下:
主要更改如下:
默认的 listen 80为 listen 443 ssl;
server_name指向之前生成服务端证书时指向的域名blog.tzx.com;
使用 ssl on开启ssl安全认证功能;
ssl_certificate指定服务端证书的地址,如/usr/local/nginx/ssl/server.crt;
ssl_certificate_key指定服务端私钥地址,如/usr/local/nginx/ssl/server.key;
ssl_session_timeout设置ssl session超时时间5m;
更改默认的跟访问路径/的路由为实际需要访问的资源,例如这里指向了tomcat默认端口(启动了tomcat才能访问,或者其他具有8080端口的可访问资源)。
原本根目录配置如下,现在注释掉:
# location / {
# root html;
# index index.html index.htm;
# }
修改之后配置如下:
location / {
proxy_pass http://172.23.130.205:8080/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
proxy_set_header HTTP_X_FORWARDED_FOR $remote_addr;
}
4.2、启动nginx
在nginx的sbin目录中启动nginx
4.3、修改客户端hosts文件,进行域名映射
由于上边的blog.tzx.com为自己虚拟的一个域名,所以电脑是不认识的,需要在客户端进行映射。
例如我这里要在自己的windows的ie浏览器中访问https://blog.tzx.com,就需要修改windows的hosts文件,C:WindowsSystem32driversetchosts,在文件中添加如下一行:
172.23.130.205 blog.tzx.com
这里blog.tzx.com为虚拟域名,前边的ip是nginx所在linux虚拟机的ip,意思是在这个windows机器中访问blog.tzx.com时,实际要访问172.23.130.205机器。
4.4、客户端安装根证书
上边启动nginx,并配置hosts之后,ie浏览器中就可以访问https://blog.tzx.com了,但是会提示不安全的站点,点击之后才能继续访问:
点击继续访问之后如下:
成功打开tomcat默认访问页面,但是url栏会提示证书错误,这是因为客户端根本无法判断服务端证书的正确性,没有依据,所以需要在浏览器安装根证书。
以ie为例,步骤依次是:
设置——》internet 选项——》内容——》证书
点击证书之后选择“受信任的根证书颁发机构”——》”导入”——》“下一步”——》“浏览”,然后选择根证书进行安装,这里的根证书即在linux中生成的ca.crt,需要先从linux中下载过来。
安装完成之后看到我们自己的CA机构信息:
清楚ie缓存,重新启动ie之后,再访问https://blog.tzx.com会看到可以直接访问了,并且没有了证书错误的提示,单向认证完毕:
5、双向认证
单向认证是客户端根据ca根证书验证服务端提供的服务端证书和私钥;双向认证还要服务端根据ca根证书验证客户端证书和私钥,因此双向认证之前还需要生成客户端证书和私钥。
5.1、生成客户端证书
客户端证书生成步骤和服务端基本一样,需要注意的就是在生成签发请求的时候填写的信息中,comm name也要是访问的域名,其他信息最好是和服务端的不一样。
客户端证书比服务端稍微多一步的就是,需要对客户端证书和私钥进行打包处理,这里方便安装以后后续访问时候携带,一般都是使用pkcs12进行打包:
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
打包时需要输入证书密码
5.2、nginx配置
双向认证,需要在nginx中开启服务端对客户端的认证,需要在server里加入如下的配置:
ssl_client_certificate /usr/local/nginx/ssl/ca.crt;
指定客户端认证时使用的根证书路径,用来验证客户端证书的正确性,作用应该等同上边浏览器安装的根证书;
ssl_verify_client on;开启客户端认证。
重启nginx,目前我的nginx没有安装成服务,因此是先kill进程,再启动。
5.3、客户端认证
上边的步骤之后,再次到ie中访问https://blog.tzx.com无法访问,结果如下:
原因就是nginx已经开启了客户端CA认证,但是这一次的访问没有携带客户端证书等信息。
解决办法就是在ie中安装之前打包的pkcs12个人证书和秘钥,之后访问的时候可以选择携带这个证书及秘钥,就可以认证通过。
安装类似之前的ca根证书安装:
设置——》internet 选项——》内容——》证书,然后选择个人中的导入,输入密码后进行安装:
证书安装完成后,再次清楚ie缓存,然后重启浏览器访问,会看到如下弹窗:
点击确定后,浏览器就会发送这个证书给nginx服务端,然后正常显示tomcat界面:
至此,整个CA双向认证过程实现完毕。
5.4、java客户端认证
以java程序作为客户端,原理是一样的,也需要客户端请求时携带客户端证书和秘钥,并且客户端需要保存根证书,用来验证服务端证书的可靠性,所以首先需要安装根证书。
5.4.1、根证书安装
根证书可以使用jdk的keytool工具安装,方式有很多种,这里只选用库文件的模式。
首先,把根证书ca.crt复制一份,重命名为ca.cer,然后把这个文件复制到jdk的jrelibsecurity目录下,在这个目录中进行根证书的安装:
keytool -keystore test.truststore -keypass 131112 -storepass 131112 -alias DemoCA -import -trustcacerts -file ca.cer
其中,test.truststore可以自定义名称,密码自定义,DemoCA别名自定义,ca.cer即刚才复制过来的文件,最后需要输入一个y确认信任。
如上图,便代表安装成功,这个证书就会被jdk信任。(理论上说,也可使用代码调用keytool安装证书)
5.4.2、java客户端代码
以下代码来自网络:https://www.cnblogs.com/dreamingodd/p/7491098.html
@Service
public class SSLService {
// 客户端证书路径,用了本地绝对路径,需要修改
private final static String CLIENT_CERT_FILE = "C:UserstzxDesktopclient.p12";
// 客户端证书密码
private final static String CLIENT_PWD = "131112";
// 信任库路径,即keytool生成的那个自定义名称的库文件
private final static String TRUST_STRORE_FILE = "D:Javajdk1.8.0_131jrelibsecuritytest.truststore";
// 信任库密码,即keytool时的密码
private final static String TRUST_STORE_PWD = "131112";
private static String readResponseBody(InputStream inputStream) throws IOException {
try {
BufferedReader br = new BufferedReader(new inputStreamReader(inputStream, Charset.forName("UTF-8")));
StringBuffer sb = new StringBuffer();
String buff = null;
while ((buff = br.readLine()) != null) {
sb.append(buff "n");
}
return sb.toString();
} finally {
inputStream.close();
}
}
public static void httpsCall() throws Exception {
// 初始化密钥库
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance("SunX509");
KeyStore keyStore = getKeyStore(CLIENT_CERT_FILE, CLIENT_PWD, "PKCS12");
keyManagerFactory.init(keyStore, CLIENT_PWD.toCharArray());
// 初始化信任库
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance("SunX509");
KeyStore trustkeyStore = getKeyStore(TRUST_STRORE_FILE, TRUST_STORE_PWD, "JKS");
trustManagerFactory.init(trustkeyStore);
// 初始化SSL上下文
SSLContext ctx = SSLContext.getInstance("SSL");
ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory
.getTrustManagers(), null);
SSLSocketFactory sf = ctx.getSocketFactory();
HttpsURLConnection.setDefaultSSLSocketFactory(sf);
String url = "https://blog.tzx.com";
URL urlObj = new URL(url);
HttpsURLConnection con = (HttpsURLConnection) urlObj.openConnection();
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36");
con.setRequestProperty("Accept-Language", "zh-CN;en-US,en;q=0.5");
con.setRequestMethod("GET");
String res = readResponseBody(con.getInputStream());
System.out.println(res);
}
/**
* 获得KeyStore
*/
private static KeyStore getKeyStore(String keyStorePath, String password, String type)
throws Exception {
FileInputStream is = new FileInputStream(keyStorePath);
KeyStore ks = KeyStore.getInstance(type);
ks.load(is, password.toCharArray());
is.close();
return ks;
}
public static void main(String[] args) throws Exception {
httpsCall(null);
}
}
运行上边的main方法,即可看到打印出字符串歌手的tomcat首页的html代码。