一、SSL简单介绍
SSL(Secure Sockets Layer 安全套接层)就是一种协议(规范),用于保障客户端和服务器端通信的安全,以免通信时传输的信息被窃取或者修改。
- 怎样保障数据传输安全?
客户端和服务器端在进行握手(客户端和服务器建立连接和交换参数的过程称之为握手)时会产生一个“对话密钥”(session key),用来加密接下来的数据传输,解密时也是用的这个“对话密钥”,而这个“对话密钥”只有客户端和服务器端知道。也就是说只要这个“对话密钥”不被破解,就能保证安全。
2. 客户端证书和服务器端证书
客户端证书和服务器端证书用于证明自己的身份,就好比每个人都有一张身份证,这种身份证是唯一的。一般来说,只要有服务器端的证书就可以了,但是有时需要客户端提供自己的证书,已证明其身份。
二、生成自签名的服务器端证书和导入服务器端信任证书库
一般证书可以使用权威机构颁发的证书,如:veri sign,百度使用的就是veri sign颁发的证书,这样的权威证书机构是受信任的,但是这些机构颁发的证书往往是需要收费的,这样的证书也难得到。对于小型企业来说为了节约成本,常常使用自签名的证书。
接下来使用JDK keytool工具来签发证书,如果未安装JDK,请先安装JDK(本文使用的是JDK7)。本文所有的证书文件都放到F:\ca,您可以选择一个目录来存放。
- 生成服务器端证书
keytool -genkeypair -v -alias server -keyalg RSA -validity 3650 -keystore ./server.keystore -storepass 123456 -keypass 123456 -dname "CN=127.0.0.1,OU=rm,O=rm,L=gz,ST=gd,C=cn"
注意:-dname参数中的CN应该为服务器所在的ip或者域名
2. 导出服务器端证书
keytool -exportcert -alias server -keystore ./server.keystore -file ./server.cer -storepass 123456
3. 将服务器端证书导入信任证书
keytool -importcert -alias serverca -keystore ./server_trust.keystore -file ./server.cer -storepass 123456
三、生成客户端证书并导入到服务器端信任证书库
- 生成客户端证书
keytool -genkeypair -v -alias client -dname "CN=rorymo" -keyalg RSA -validity 3650 -keypass 123456 -keystore ./client.p12 -storepass 123456 -storetype PKCS12
2. 导出客户端证书
keytool -exportcert -alias client -file ./client.cer -keystore ./client.p12 -storepass 123456 -storetype PKCS12
3. 导入客户端证书到服务器端信任证书库
keytool -importcert -alias clientca -keystore ./server_trust.keystore -file ./client.cer -storepass 123456
4. 查看服务器端信任证书库的信任证书信息
keytool -list -keystore ./server_trust.keystore -storepass 123456
可以看到信任证书库中已经导入了一张服务器端证书和一张客户端证书
5. 至此我们已经生成了如下的文件
四、配置tomcat和web应用
- 将证书文件放到tomcat根目录下
将上图的server.keystore和server_trust.keystore放到tomcat的根目录下,例如我的tomcat目录为:F:\ca\apache-tomcat-7.0.64
2. 配置tomcat
编辑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="${catalina.base}/server.keystore" keystorePass="123456" truststoreFile ="${catalina.base}/server_trust.keystore" truststorePass="123456"/>
说明:
- clientAuth为true表示开启SSL双向认证
- keystoreFile指定服务器端的证书位置
- truststoreFile指定服务器端信任证书库
3. 编写用来获取客户端证书的servlet
1 package com.rorymo.demo.ssl; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 import java.security.cert.X509Certificate; 6 7 import javax.servlet.ServletException; 8 import javax.servlet.annotation.WebServlet; 9 import javax.servlet.http.HttpServlet; 10 import javax.servlet.http.HttpServletRequest; 11 import javax.servlet.http.HttpServletResponse; 12 13 /** 14 * 15 * SSLServlet 16 * 17 * @author rorymo 18 * @version 1.0 19 */ 20 @WebServlet("/SSLServlet") 21 public class SSLServlet extends HttpServlet { 22 23 private static final long serialVersionUID = 1601507150278487538L; 24 private static final String REQUEST_ATTR_CERT = "javax.servlet.request.X509Certificate"; 25 private static final String CONTENT_TYPE = "text/plain;charset=UTF-8"; 26 private static final String DEFAULT_ENCODING = "UTF-8"; 27 private static final String SCHEME_HTTPS = "https"; 28 29 public void doGet(HttpServletRequest request, HttpServletResponse response) 30 throws ServletException, IOException { 31 response.setContentType(CONTENT_TYPE); 32 response.setCharacterEncoding(DEFAULT_ENCODING); 33 PrintWriter out = response.getWriter(); 34 X509Certificate[] certs = (X509Certificate[]) request.getAttribute(REQUEST_ATTR_CERT); 35 if (certs != null) { 36 int count = certs.length; 37 out.println("共检测到[" + count + "]个客户端证书"); 38 for (int i = 0; i < count; i++) { 39 X509Certificate cert = certs[i]; 40 out.println("客户端证书 [" + cert.getSubjectDN() + "]: "); 41 out.println("证书是否有效:" + (verifyCertificate(cert) ? "是" : "否")); 42 out.println("证书详细信息:\r" + cert.toString()); 43 } 44 } else { 45 if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) { 46 out.println("这是一个HTTPS请求,但是没有可用的客户端证书"); 47 } else { 48 out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表 "); 49 } 50 } 51 out.close(); 52 } 53 54 public void doPost(HttpServletRequest request, HttpServletResponse response) 55 throws ServletException, IOException { 56 doGet(request, response); 57 } 58 59 /** 60 * 61 * 校验证书是否过期 62 * 63 * 64 * @param certificate 65 * @return 66 */ 67 private boolean verifyCertificate(X509Certificate certificate) { 68 boolean valid = true; 69 try { 70 certificate.checkValidity(); 71 } catch (Exception e) { 72 e.printStackTrace(); 73 valid = false; 74 } 75 return valid; 76 } 77 78 }
4. 在web应用的web.xml中加入如下配置
<security-constraint> <web-resource-collection> <web-resource-name>SSLweb-resource-name> <url-pattern>/SSLServleturl-pattern> web-resource-collection> <user-data-constraint> <description>SSL requireddescription> <transport-guarantee>CONFIDENTIALtransport-guarantee> user-data-constraint> security-constraint>
说明:
- 如果不加入这个配置,那么所有访问的地址都必须要使用SSL才能访问,有时我们可能只需要通过某个或者某些SSL地址获取客户端证书来认证用户身份,认证成功后不需要使用SSL来进行访问。(可以配置多个security-constraint)
- url-pattern:指定需要SSL才能进行访问的地址
- transport-guarantee:合法值为NONE、 INTEGRAL或CONFIDENTIAL,transport-guarantee为NONE值将对所用的通讯协议不加限制。INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。虽然原理上(并且在未来的HTTP版本中),在 INTEGRAL和CONFIDENTIAL之间可能会有差别,但在当前实践中,他们都只是简单地要求用SSL
- 创建SSLServlet获取客户端证书
五、测试
- 用浏览器访问http://127.0.0.1:8080/SSL/SSLServlet,细心的读者可能发现链接已经跳转到了https://127.0.0.1:8443/SSL/SSLServlet ,这是由于这个地址被设置为需要SSL才能访问,所以跳转到了这个地址。访问时页面提示如下:
由于我们这里是双向认证,所以也需要客户端的证书,我们接下来导入客户端证书:
- 双击client.p12
- 点击下一步
- 一直点击下一步直到完成证书安装(按照提示进行操作即可)
- 查看安装的客户端证书,由于我使用的Google Chrome浏览器(其他浏览器可能不同),设置 -> 高级设置 -> 管理证书
2. 重新访问上面的地址:http://127.0.0.1:8080/SSL/SSLServlet
浏览器提示选择客户端证书进行认证,我们点击【确定】,打开了一个警告页面,提示我们服务端证书不受信任
而且地址栏的图标有个小红叉:
当然我们直接点击是能打开的
为了不出现这样的警告信息,我们可以导入服务器端证书到客户端
a. 导入服务器端证书到客户端
-
- 双击server.cer
-
- 点击安装证书,然后选择证书的存储位置“受信任的根证书颁发机构”,一直点击下一步直到完成
-
- 查看我们安装的服务器端证书
b. 关闭浏览器,我们再访问
六、附录
- Keytool命令常用参数说明:
-
- -genkeypair在用户主目录中创建一个默认文件”.keystore”,还会产生一个mykey的别名,mykey中包含用户的公钥、私钥和证书(在没有指定生成位置的情况下,keystore会存在用户系统默认目录)
- -alias 产生别名 每个keystore都关联这一个独一无二的alias,这个alias通常不区分大小写
- -keystore 指定密钥库的路径(产生的各类信息将不在.keystore文件中)
- -keyalg 指定密钥的算法 (如 RSA,DSA,默认值为:DSA)
- -validity 指定创建的证书有效期多少天(默认 90)
- -keysize 指定密钥长度 (默认 1024
- -storepass 指定密钥库的密码(获取keystore信息所需的密码)
- -keypass 指定别名条目的密码(私钥的密码)
- -dname 指定证书发行者信息 其中: “CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名 称,ST=州或省份名称,C=单位的两字母国家代码”
- -list 显示密钥库中的证书信息如:keytool -list -v –keystore path/to/keystore -storepass password
- -v 显示密钥库中的证书详细信息
- -exportcert 导出指定别名的证书,如:keytool - exportcert -alias theAlias -keystore path/to/keystore -file path/to/keystore/cert -storepass pass
- -file 参数指定导出到文件的文件名
- -delete 删除密钥库中某条目 keytool -delete -alias theAlias -keystore path/to/keystore –storepass pass
- -printcert 控制台打印证书的详细信息,如:keytool -printcert -file path/to/keystore/cert -v
- -keypasswd 修改密钥库中指定条目口令 keytool -keypasswd -alias theAlias -keypass oldPass -new newPass -storepass keystorePass -keystore path/to/keystore
- -storepasswd 修改keystore口令 keytool -storepasswd -keystore path/to/keystore -storepass oldPass -new newPass
- -importcert 将已签名数字证书导入密钥库 keytool -importcert -alias certAlias -keystore path/to/keystore -file path/to/keystore/cert
2. 本文源码下载
【完】
感谢您的阅读