兩臺Tomcat服務器之間進行SSL雙向認證

這個東西困擾了我得有兩個星期的時間。下面直接上過程吧。

一,製作證書

首先我們準備兩個項目(沒有問題可訪問的)及兩臺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配置文件,配置如下:
打開第二個服務器的相應配置文件,配置如下
下面是我的服務器目錄示例,兩臺服務器一樣的目錄(我將G盤製作的證書複製到了此處的key目錄下):
在項目的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;
    }
    
}

客戶端訪問結果如下:

客戶端的訪問結果不重要,重要的是服務端的訪問結果如何,如下:

看紅框圈起來的部分,輸出了內容,並且在這幾句輸出內容時沒有報bug,那麼則就說明此次的https請求是成功的

下面的bug是參數問題,我沒有在客戶端傳遞參數,但是服務端需要獲取參數,所以出了bug。

到這裏大家是不是已經發現其實 只用了一臺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。

如有不全之處,還望大家多多海涵,不喜勿噴。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章