这个东西困扰了我得有两个星期的时间。下面直接上过程吧。

一,制作证书

首先我们准备两个项目(没有问题可访问的)及两台Tomcat服务器(未经修改的)并测试相互访问(http请求即可)

1,生成服务器证书库

keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore G:\ssl\server.keystore -dname "CN=127.0.0.1,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456

2,生成客户端证书库

keytool -validity 365 -genkeypair -v -alias client -keyalg RSA -storetype PKCS12 -keystore G:\ssl\client.p12 -dname "CN=client,OU=icesoft,O=icesoft,L=Haidian,ST=Beijing,c=cn" -storepass 123456 -keypass 123456

3,从客户端证书库中导出客户端证书

keytool -export -v -alias client -keystore G:\ssl\client.p12 -storetype PKCS12 -storepass 123456 -rfc -file G:\ssl\client.cer

4,从服务器证书库中导出服务器证书

keytool -export -v -alias server -keystore G:\ssl\server.keystore -storepass 123456 -rfc -file G:\ssl\server.cer

5,生成客户端信任证书库

keytool -import -v -alias server -file G:\ssl\server.cer -keystore G:\ssl\client.truststore -storepass 123456

6,将客户端证书导入到服务器证书库(使得服务器信任客户端证书)

keytool -import -v -alias client -file G:\ssl\client.cer -keystore G:\ssl\server.keystore -storepass 123456

7,查看证书库中的全部证书

keytool -list -keystore G:\ssl\server.keystore

二,配置Tomcat

打开Tomcat服务器的server.xml配置文件,配置如下:

charles 双向认证 import pem证书_浏览器与服务器之间的双向认证

打开第二个服务器的相应配置文件,配置如下

charles 双向认证 import pem证书_服务器之间的双向认证_02

下面是我的服务器目录示例,两台服务器一样的目录(我将G盘制作的证书复制到了此处的key目录下):

charles 双向认证 import pem证书_java_03

charles 双向认证 import pem证书_浏览器与服务器之间的双向认证_04

在项目的web.xml文件中加入如下配置(客户和服务都加,保险起见):

 <!-- 强制SSL配置,即普通的请求也会重定向为SSL请求 -->  

    <security-constraint>

        <web-resource-collection>

            <web-resource-name>SSL</web-resource-name>

            <url-pattern>/*</url-pattern><!-- 全站使用SSL -->

        </web-resource-collection>

        <user-data-constraint>

            <description>SSL required</description>

            <!-- CONFIDENTIAL: 要保证服务器和客户端之间传输的数据不能够被修改,且不能被第三方查看到 -->

            <!-- INTEGRAL: 要保证服务器和client之间传输的数据不能够被修改 -->

            <!-- NONE: 指示容器必须能够在任一的连接上提供数据。(即用HTTP或HTTPS,由客户端来决定)-->

            <transport-guarantee>CONFIDENTIAL</transport-guarantee>

        </user-data-constraint>

    </security-constraint>

备注:

keystoreFile:指定服务器密钥库,可以配置成绝对路径,如“D:/key/server.keystore”,本例中是在Tomcat目录中创建了一个名称为key的文件夹,仅供参考。
keystorePass:密钥库生成时的密码
truststoreFile:受信任密钥库,和密钥库相同即可
truststorePass:受信任密钥库密码

三,项目演示

找个项目作为客户端发起请求(代码如下):

package com.icesoft.client;
import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.security.KeyStore;import org.apache.http.HttpEntity;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.HttpClient;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.conn.scheme.Scheme;
 import org.apache.http.conn.ssl.SSLSocketFactory;
 import org.apache.http.impl.client.DefaultHttpClient;
 import org.apache.http.util.EntityUtils;public class HttpsClient {
     
     private static final String KEY_STORE_TYPE_JKS = "jks";
     private static final String KEY_STORE_TYPE_P12 = "PKCS12";
     private static final String SCHEME_HTTPS = "https";
     private static final int HTTPS_PORT = 8443;
     private static final String HTTPS_URL = "https://127.0.0.1:8443/afinBusiness/admin/user/login";
     
     private static final String KEY_STORE_CLIENT_PATH = "C:/Users/ASUS/Desktop/服务器/keytoolWorkSpace/apache-tomcat-8.0.15_01/key/client.p12";
     private static final String KEY_STORE_TRUST_PATH = "C:/Users/ASUS/Desktop/服务器/keytoolWorkSpace/apache-tomcat-8.0.15_01/key/client.truststore";
     private static final String KEY_STORE_PASSWORD = "123456";
     private static final String KEY_STORE_TRUST_PASSWORD = "123456";
     
     public static void main(String[] args) {
         try {
             ssl();
         } catch (Exception e) {
             System.out.println("异常一");
             e.printStackTrace();
         }
     }
     private static void ssl() throws Exception {
         HttpClient httpClient = new DefaultHttpClient();
         try {
             KeyStore keyStore  = KeyStore.getInstance(KEY_STORE_TYPE_P12);
             KeyStore trustStore  = KeyStore.getInstance(KEY_STORE_TYPE_JKS);
             InputStream ksIn = new FileInputStream(KEY_STORE_CLIENT_PATH);
             InputStream tsIn = new FileInputStream(new File(KEY_STORE_TRUST_PATH));
             try {
                 keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
                 trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
             } finally {
                 try { ksIn.close(); } catch (Exception ignore) {}
                 try { tsIn.close(); } catch (Exception ignore) {}
             }
             SSLSocketFactory socketFactory = new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore);
             Scheme sch = new Scheme(SCHEME_HTTPS, HTTPS_PORT, socketFactory);
             httpClient.getConnectionManager().getSchemeRegistry().register(sch);
             HttpGet httpget = new HttpGet(HTTPS_URL);
             System.out.println("executing request" + httpget.getRequestLine());
             HttpResponse response = httpClient.execute(httpget);
             HttpEntity entity = response.getEntity();
             System.out.println("----------------------------------------");
             System.out.println(response.getStatusLine());
             if (entity != null) {
                 System.out.println("Response content length: " + entity.getContentLength());
                 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent()));
                 String text;
                 while ((text = bufferedReader.readLine()) != null) {
                     System.out.println(text);
                 }
                 bufferedReader.close();
             }
             EntityUtils.consume(entity);
         } finally {
             httpClient.getConnectionManager().shutdown();
         }
     }}

找个项目放在Tomcat_01服务器上当服务端:代码如下

import java.io.IOException;
 import java.util.Map;import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;import com.alibaba.fastjson.JSON;
 import com.qtong.afinance.core.domain.ResultObject;
 import com.qtong.afinance.core.util.HttpTool;
 import com.qtong.afinance.module.service.admin.AdminLoginService;@Controller
 @RequestMapping("/admin/user")
 public class AdminLoginController {
     @Autowired
     private AdminLoginService loginService;
     
     
     private static final long serialVersionUID=1601507150278487538L;
     
     private static final String ATTR_CER="javax.servlet.request.X509Certificate";
     
     private static final String CONTENT_TYPE="text/plain;charset=UTF-8";
     
     private static final String DEFAULT_ENCODING="UTF-8";
     
     private static final String SCHEME_HTTPS="https";
     
     @RequestMapping("/login")
     @ResponseBody
     public ResultObject login(HttpServletRequest req,HttpServletResponse resp) throws IOException{
         System.out.println("已经访问到了,老子成功了");
         System.out.println("无加工输出的请求为:"+req);
         
         
         String reqMess=HttpTool.javaProtogenesisGetRequest(req);
         System.out.println("加工后输出的请求为:"+reqMess);
         Map map=JSON.parseObject(reqMess,Map.class);
         String mobile=(String) map.get("mobile");
         String password=(String) map.get("password");
         
         System.out.println("在这里获取到的参数为>>>账号:"+mobile+"密码为:"+password);
         return null;
     }
     
 }

客户端访问结果如下:

charles 双向认证 import pem证书_浏览器与服务器之间的双向认证_05

客户端的访问结果不重要,重要的是服务端的访问结果如何,如下:

看红框圈起来的部分,输出了内容,并且在这几句输出内容时没有报bug,那么则就说明此次的https请求是成功的

下面的bug是参数问题,我没有在客户端传递参数,但是服务端需要获取参数,所以出了bug。

charles 双向认证 import pem证书_服务器_06

到这里大家是不是已经发现其实 只用了一台Tomcat服务器,和本文的主题不想符合,但是如果将客户端和服务端换下位置,1号服务器当客户端,2号服务器当服务端,那么则就与本文符合了那么一些(语文水平就这么高,凑合着看吧)。到这里服务之间的双向认证已经完成。不过在送给大家一端代码,可以直接放在服务端的方法里直接运行,如下:(查看证书的一段代码)

import java.io.IOException;
 import java.io.PrintWriter;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
 import java.security.cert.X509Certificate;
 import java.util.Map;import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.ResponseBody;import com.alibaba.fastjson.JSON;
 import com.qtong.afinance.core.domain.ResultObject;
 import com.qtong.afinance.core.util.HttpTool;

 @Controller
 @RequestMapping("/admin/user")
 public class AdminLoginController {
     
     private static final long serialVersionUID=1601507150278487538L;
     
     private static final String ATTR_CER="javax.servlet.request.X509Certificate";
     
     private static final String CONTENT_TYPE="text/plain;charset=UTF-8";
     
     private static final String DEFAULT_ENCODING="UTF-8";
     
     private static final String SCHEME_HTTPS="https";
     
     @RequestMapping("/login")
     @ResponseBody
     public ResultObject login(HttpServletRequest req,HttpServletResponse resp) throws IOException{
     
         resp.setContentType(CONTENT_TYPE);
         resp.setCharacterEncoding(DEFAULT_ENCODING);
         PrintWriter out = resp.getWriter();
         X509Certificate[] certs=(X509Certificate[])req.getAttribute(ATTR_CER);
         if(certs!=null){
             int count=certs.length;
             out.println("共检测到["+count+"]个客户端证书。。。");
             System.out.println("************分割线************");
             System.out.println("共检测到["+count+"]个客户端证书。。。");
             for(int i=0; i< count; i++){
                 out.println("客户端证书["+(++i)+"]:");
                 out.println("校验结果:"+verifyCertificate(certs[--i]));
                 out.println("证书详细:\r"+certs[i].toString());
                 System.out.println("************分割线************");
                 System.out.println("客户端证书["+(++i)+"]:");
                 System.out.println("校验结果:"+verifyCertificate(certs[--i]));
                 System.out.println("证书详细:\r"+certs[i].toString());
             }
         }else{
             if(SCHEME_HTTPS.equalsIgnoreCase(req.getScheme())){
                 out.println("这是一个HTTPS请求,但是没有可用的客户端证书。。。");
                 System.out.println("************分割线************");
                 System.out.println("这是一个HTTPS请求,但是没有可用的客户端证书。。。");
             }else{
                 out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表。。。");
                 System.out.println("************分割线************");
                 System.out.println("这不是一个HTTPS请求,因此无法获得客户端证书列表。。。");
             }
         }
         out.close();
         
         
         
         
         System.out.println("已经访问到了,老子成功了");
         System.out.println("无加工输出的请求为:"+req);
         
         
         String reqMess=HttpTool.javaProtogenesisGetRequest(req);
         System.out.println("加工后输出的请求为:"+reqMess);
         Map map=JSON.parseObject(reqMess,Map.class);
         String mobile=(String) map.get("mobile");
         String password=(String) map.get("password");
         System.out.println("在这里获取到的参数为>>>账号:"+mobile+"密码为:"+password);
         return null;
     }
     
     //校验证书是否过期
         private boolean verifyCertificate(X509Certificate certificate){
             boolean valid=true;
             try {
                 certificate.checkValidity();
             } catch (CertificateExpiredException e) {
                 e.printStackTrace();
             } catch (CertificateNotYetValidException e) {
                 e.printStackTrace();
             }
             return valid;
         }
     
 }

下面是我参考资料帖子:这位博主很强

http://www.blogjava.net/icewee/archive/2012/06/04/379947.html       这个实现了浏览器与服务端的双向认证

http://www.blogjava.net/icewee/archive/2012/06/05/379983.html        HTTPS请求代码的提供帖

这些是我在饱受折磨后得出来的结果在此分享给大家,貌似也没有什么,可是每一个成果从无到有的路途都不是一帆风顺的,祝大家项目上线必火,永无bug。

如有不全之处,还望大家多多海涵,不喜勿喷。