Java Tomcat SSL 服務端/客戶端雙向認證



SSL——Secure Sockets Layer

雙向認證(個人理解):
客戶端認證:
客戶端通過瀏覽器訪問某一網站時,如果該網站爲HTTPS網站,瀏覽器會自動檢測系統中是否存在該網站的信任證書,如果沒有信任證書,瀏覽器一般會拒絕訪問,IE會有一個繼續訪問的鏈接,但地址欄是紅色,給予用戶警示作用,即客戶端驗證服務端並不是強制性的,可以沒有服務端的信任證書,當然是否繼續訪問完全取決於用戶自己。如何去除地址欄的紅色警告呢?後續會介紹導入服務端證書到瀏覽器的方法。

服務端認證:
服務端需要獲取到客戶端通過瀏覽器發送過來的認證證書,該證書在服務端的證書庫中已存在,僅僅是個匹配過程,匹配成功即通過認證,可繼續訪問網站資源,反之則無法顯示網頁,後續有截圖。

基本邏輯:
1、生成服務端密鑰庫並導出證書;
2、生成客戶端密鑰庫並導出證書;
3、根據服務端密鑰庫生成客戶端信任的證書;
4、將客戶端證書導入服務端密鑰庫;
5、將服務端證書導入瀏覽器。

構建演示系統
演示環境:
JDK:1.6.0_32
Tomcat:apache-tomcat-7.0.27
開發工具:MyEclipse 10
瀏覽器:Internet Explorer 9

一、生成密鑰庫和證書
可參考以下密鑰生成腳本,根據實際情況做必要的修改,其中需要注意的是:服務端的密鑰庫參數“CN”必須與服務端的IP地址相同,否則會報錯,客戶端的任意。
key.script

1、生成服務器證書庫

keytool -validity 365 -genkey -v -alias server -keyalg RSA -keystore E:\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 E:\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 E:\ssl\client.p12 -storetype PKCS12 -storepass 123456 -rfc -file E:\ssl\client.cer


4、從服務器證書庫中導出服務器證書

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


5、生成客戶端信任證書庫(由服務端證書生成的證書庫)

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


6、將客戶端證書導入到服務器證書庫(使得服務器信任客戶端證書)

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


7、查看證書庫中的全部證書

keytool -list -keystore E:\ssl\server.keystore -storepass 123456



二、Tomat配置
使用文本編輯器編輯${catalina.base}/conf/server.xml
找到Connector port="8443"的標籤,取消註釋,並修改成如下:

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" SSLEnabled="true"
               maxThreads
="150" scheme="https" secure="true"
               clientAuth
="true" sslProtocol="TLS"
               keystoreFile
="${catalina.base}/key/server.keystore" keystorePass="123456"
               truststoreFile
="${catalina.base}/key/server.keystore" truststorePass="123456"/>


備註:
keystoreFile:指定服務器密鑰庫,可以配置成絕對路徑,如“D:/key/server.keystore”,本例中是在Tomcat目錄中創建了一個名稱爲key的文件夾,僅供參考。
keystorePass:密鑰庫生成時的密碼
truststoreFile:受信任密鑰庫,和密鑰庫相同即可
truststorePass:受信任密鑰庫密碼

三、建立演示項目
項目結構圖:
項目名稱:SSL(隨意)


SSLServlet.java

package com.icesoft.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.security.cert.X509Certificate;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>
 * SSL Servlet
 * </p>
 * 
 * 
@author  IceWee
 * @date 2012-6-4
 * 
@version  1.0
 
*/

public class SSLServlet extends HttpServlet {

    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";

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType(CONTENT_TYPE);
        response.setCharacterEncoding(DEFAULT_ENCODING);
        PrintWriter out = response.getWriter();
        X509Certificate[] certs = (X509Certificate[]) request.getAttribute(ATTR_CER);
        if (certs != null{
            int count = certs.length;
            out.println("共檢測到[" + count + "]個客戶端證書");
            for (int i = 0; i < count; i++) {
                out.println("客戶端證書 [" + (++i) + "]: ");
                out.println("校驗結果:" + verifyCertificate(certs[--i]));
                out.println("證書詳細:\r" + certs[i].toString());
            }

        }
 else {
            if (SCHEME_HTTPS.equalsIgnoreCase(request.getScheme())) {
                out.println("這是一個HTTPS請求,但是沒有可用的客戶端證書");
            }
 else {
                out.println("這不是一個HTTPS請求,因此無法獲得客戶端證書列表 ");
            }

        }

        out.close();
    }


    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

    
    /**
     * <p>
     * 校驗證書是否過期
     * </p>
     * 
     * 
@param  certificate
     * 
@return
     
*/

    private boolean verifyCertificate(X509Certificate certificate) {
        boolean valid = true;
        try {
            certificate.checkValidity();
        }
 catch (Exception e) {
            e.printStackTrace();
            valid = false;
        }

        return valid;
    }


}


web.xml
說明:該演示項目強制使用了SSL,即普通的HTTP請求也會強制重定向爲HTTPS請求,配置在最下面,可以去除,這樣HTTP和HTTPS都可以訪問。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 
    xmlns
="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation
="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
>
      <display-name>Secure Sockets Layer</display-name>    
    
    <servlet>
        <servlet-name>SSLServlet</servlet-name>
        <servlet-class>com.icesoft.servlet.SSLServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>SSLServlet</servlet-name>
        <url-pattern>/sslServlet</url-pattern>
    </servlet-mapping>
    
    <welcome-file-list>
      <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- 強制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>
</web-app>



index.jsp

<%@ page language="java" pageEncoding="UTF-8"%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>客戶端證書上傳</title>
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">    
</head>
<body>
<form action="${pageContext.request.contextPath}/sslServlet" method="post">
    <input type="submit"  value="提交證書"/>
</form>
</body>
</html>

發佈了98 篇原創文章 · 獲贊 87 · 訪問量 67萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章