1. 程式人生 > >JAVA基於HTTPS的加密遠端呼叫的實現

JAVA基於HTTPS的加密遠端呼叫的實現

ServicePlatform-v2最終通過HTTPS強加密實現了通訊的安全傳輸,畢竟傳輸的都是關鍵的使用者賬號,而且我們現在還沒有自己的專有網路.
ServicePlatform 的RMI使用的輕量級Hessian或Burlup協議是基於HTTP協議的,如果部署得當,在沒有惡意竊聽的情況下是不會出現使用者資訊洩漏的問題, 但是一旦平臺啟用後RMI呼叫被惡意截獲,那麼在request裡設計的註冊服務的密碼驗證和使用者資訊都將實效,SP平臺的介面暴露,使用者資訊洩漏. 徹底的解決方法就是強制HTTPS方式訪問RMI介面,而HTTPS對於HTTP是協議本身是透明的(所謂巢狀層的由來),所以HTTPS可以在不修改 SP的程式碼的(配置描述符的修改當然避免不了)情況下實現RMI通訊的強加密,而且也確實如此.
ServicePlatform的預設部署是強制 HTTPS訪問RMI介面,使用ServicePlatform的客戶端除了需要配置為HTTPS的invoker的地址外,還需要獲得 ServicePlatform的證書,否則JSSE將不會正常工作.下面是部署描述符web.xml裡面強制HTTPS訪問的部分.

<!-- comment below to enable plain HTTP invoker rather than HTTPS only -->
<security-constraint>
    <web-resource-collection>
        <web-resource-name>remote</web-resource-name>
        <url-pattern>/remote/*</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
        <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

ServicePlatform的證書的生成由Java的keygen完成
%JAVA_HOME%\bin\keytool -genkey -alias tomcat -keypass changeit -keyalg RSA -validity 365
其中,changeit為金鑰的生成密碼,需要與tomcat的伺服器部署描述符一致,參考下面的server.xml的片斷
<Connector port="8443" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" disableUploadTimeout="true"
               acceptCount="100" scheme="https" secure="true"
               clientAuth="false" sslProtocol="TLS" keystorePass="changeit" keystoreFile="conf/keystore"/>

生成證書時必須填寫正確的ServicePlatform執行的伺服器的FQDN,然後和service-dist.properties檔案中的invoker的URL的FQDN完全一致,
否則JSSE將證書視為無效,無法建立HTTPS連線,然後在啟動客戶端的時候需要新增指向證書的JVM引數
-Djavax.net.ssl.trustStore=path\to\your\generated\keystore\file

應用伺服器和JDK版本資訊
Application Server tomcat-5.5.15
JDK 1.5.0_06

Java應用Tomcat中實現https安全連線的方法

SSL, 或者Secure Socket Layer,是一種允許web瀏覽器和web伺服器通過一個安全的連線進行交流的技術。這意味著將被髮送的資料在一端被翻譯成密碼,傳送出去,然後在另一端解開密碼,再進行處理。這是一個雙向的過程,也就是瀏覽器和伺服器都需要在傳送資料之前對它們進行加密。

SSL協定的另一個重要方面是認證(Authentication)。這就是說,在你開始試圖通過一個安全連線與一個web伺服器交流的時候,這個伺服器會要求你的瀏覽器出示一組證件,通過“鑑定”的方式來證明這就是你所宣告的網站。

在某些情況下,伺服器還會要求你的web瀏覽器的認證書,證明你就是你所說的那個人。這就是所知的“客戶認證”,儘管實際情況中,更多地用在商務-對-商務(B2B)交易,而不是對個人使用者。

但大多數有SSL功能的web伺服器不要求客戶認證(Client Authentication)。

證書

為了能實施SSL,一個web伺服器對每個接受安全連線的外部介面(IP 地址)必須要有相應的證書(Certificate)。關於這個設計的理論是一個伺服器必須提供某種合理的保證以證明這個伺服器的主人就是你所認為的那個人。這個證書要陳述與這個網站相關聯的公司,以及這個網站的所有者或系統管理員的一些基本聯絡資訊。

這個證書由所有人以密碼方式簽字,其他人非常難偽造。對於進行電子商務(e-commerce)的網站,或其他身份認證至關重要的任何商業交易,認證書要向大家所熟知的認證權威(Certificate Authority (CA))如VeriSign或Thawte來購買。這樣的證書可用電子技術證明屬實。實際上,認證權威單位會擔保它發出的認證書的真實性,如果你信任發出認證書的認證權威單位的話,你就可以相信這個認證書是行У摹?/font>

在許多情況下,認證並不是真正使人擔憂的事。系統管理員或許只想要保證被伺服器傳送和接收的資料是祕密的,不會被連線線上的偷竊者盜竊到。慶幸的是,Java提供相對簡單的被稱為keytool的命令列工具,可以簡單地產生“自己簽名”的證書。自己簽名的證書只是使用者產生的證書,沒有正式在大家所熟知的認證權威那裡註冊過,因此不能確保它的真實性。但卻能保證資料傳輸的安全性。

認證也許很重要,也許不重要,完全決定於網站的需要。

用Tomcat來配置SSL主要有下面這麼兩大步驟:

一、生成證書

1、 在命令列下執行:

%Java_home%\bin\keytool -genkey -alias tomcat -keyalg RSA

在此命令中,keytool是JDK自帶的產生證書的工具。把RSA運演算法則作為主要安全運演算法則,這保證了與其它伺服器和元件的相容性。

這個命令會在使用者的home directory產生一個叫做" .keystore " 的新檔案。在執行後,你首先被要求出示keystore密碼。Tomcat使用的預設密碼是" changeit "(全都是小寫字母),如果你願意,你可以指定你自己的密碼。你還需要在server.xml配置檔案裡指定自己的密碼,這在以後會有描述。

2、 你會被要求出示關於這個認證書的一般性資訊,如公司,聯絡人名稱,等等。這些資訊會顯示給那些試圖訪問你程式裡安全網頁的使用者,以確保這裡提供的資訊與他們期望的相對應。

3、 你會被要求出示金鑰(key)密碼,也就是這個認證書所特有的密碼(與其它的儲存在同一個keystore檔案裡的認證書不同)。你必須在這裡使用與keystore密碼相同的密碼。(目前,keytool會提示你按ENTER鍵會自動幫你做這些)。

如果一切順利,你現在就擁有了一個可以被你的伺服器使用的有認證書的keystore檔案。

二、配置tomcat

第二個大步驟是把secure socket配置在$CATALINA_HOME/conf/server.xml檔案裡。$CATALINA_HOME代表安裝Tomcat的目錄。一個例子是SSL聯結器的元素被包括在和Tomcat一起安裝的預設server.xml檔案裡。它看起來象是這樣:

$CATALINA_HOME/conf/server.xml

< -- Define a SSL Coyote HTTP/1.1 Connector on port 8443 -->

< !--

< Connector

port="8443" minProcessors="5" maxProcessors="75"

enableLookups="true" disableUploadTimeout="true"

acceptCount="100" debug="0" scheme="https" secure="true";

clientAuth="false" sslProtocol="TLS"/>

-->

Connector元素本身,其預設形式是被註釋掉的(commented out),所以需要把它周圍的註釋標誌刪除掉。然後,可以根據需要客戶化(自己設定)特定的屬性。一般需要增加一下keystoreFile和keystorePass兩個屬性,指定你存放證書的路徑(如:keystoreFile="C:/.keystore")和剛才設定的密碼(如:keystorePass="123456")。關於其它各種選項的詳細資訊,可查閱Server Configuration Reference。

在完成這些配置更改後,必須象重新啟動Tomcat,然後你就可以通過SSL訪問Tomcat支援的任何web應用程式。只不過指令需要像下面這樣:https://localhost:8443

使用SSL構建安全的Socket

SSL(安全套接層)是Netscape公司在1994年開發的,最初用於WEB瀏覽器,為瀏覽器與伺服器間的資料傳遞提供安全保障,提供了加密、來源認證和資料完整性的功能。現在SSL3.0得到了普遍的使用,它的改進版TLS(傳輸層安全)已經成為網際網路標準。SSL本身和TCP套接字連線是很相似的,在協議棧中,SSL可以被簡單的看作是安全的TCP連線,但是某些TCP連線的特性它是不支援的,比如帶外資料(out-of-bound)。

在構建基於Socket的C/S程式時,通過新增對SSL的支援來保障資料安全和完整是不錯的方法。完善的Java為我們提供了簡單的實現方法:JSSE(Java安全套接字擴充套件)。JSSE是一個純Java實現的SSL和TLS協議框架,抽象了SSL和TLS複雜的演算法,使安全問題變得簡單。JSSE已經成為J2SE1.4版本中的標準組件,支援SSL 3.0和TLS 1.0。我們將通過一個具體的例子演示JSSE的一些基本應用。例子中的伺服器端將開啟一個SSL Socket,只有持有指定證書的客戶端可以與它連線,所有的資料傳遞都是加密的。

構造一個SSLSocket是非常簡單的:

SSLServerSocketFactory factory=(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(portNumber);
SSLSocket socket = (SSLSocket);

但是執行這樣的程式會產生一個異常,報告找不到可信任的證書。SSLSocket和普通的Socket是不一樣的,它需要一個證書來進行安全認證。

一、 證書

生成一個CA證書,在命令列下執行:

keytool –genkey –keystore SSLKey –keyalg rsa –alias SSL

黑體部分是使用者可以自己指定的引數,第一個引數是要生成的證書的名字,第二個引數是證書的別名。rsa指明瞭我們使用的加密方法。

系統會要求輸入證書發放者的資訊,逐項輸入即可,如下圖:

    系統生成的檔案命將會和證書名相同。證書可以提交給權威CA認證組織稽核,如果通過稽核,組織會提供信任擔保,向客戶擔保你的連線是安全的。當然這不是必須的。在我們的例子中會把證書直接打包到客戶端程式中,保證客戶端是授權使用者,避免偽造客戶,所以不需要提交稽核。

二、 伺服器端

現在可以編寫伺服器端的程式碼,與普通的Socket程式碼不同,我們需要在程式中匯入證書,並使用該證書構造SSLSocket。需要的說明的是:

●KeyStore ks=KeyStore.getInstance("JKS");

訪問Java金鑰庫,JKS是keytool建立的Java金鑰庫,儲存金鑰。

● KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");

建立用於管理JKS金鑰庫的X.509金鑰管理器。

● SSLContext sslContext=SSLContext.getInstance("SSLv3");

構造SSL環境,指定SSL版本為3.0,也可以使用TLSv1,但是SSLv3更加常用。

●sslContext.init(kmf.getKeyManagers(),null,null);

初始化SSL環境。第二個引數是告訴JSSE使用的可信任證書的來源,設定為null是從javax.net.ssl.trustStore中獲得證書。第三個引數是JSSE生成的隨機數,這個引數將影響系統的安全性,設定為null是個好選擇,可以保證JSSE的安全性。

完整程式碼如下:

/*
*SSL Socket的伺服器端
*@Author Bromon
*/

package org.ec107.ssl;

import java.net.*;
import javax.net.ssl.*;
import java.io.*;
import java.security.*;

public class SSLServer
{
   static int port=8266;   //系統將要監聽的埠號,82.6.6是偶以前女朋友的生日^_^
   static SSLServerSocket server;
  
   /*
   *建構函式
   */
  
   public SSLServer()
   {
   
   }
  
  
   /*
   *@param port 監聽的埠號
   *@return 返回一個SSLServerSocket物件
   */
  
   private static SSLServerSocket getServerSocket(int thePort)
   {
    SSLServerSocket s=null;
    try
    {
     String key="SSLKey";   //要使用的證書名

     char keyStorePass[]="12345678".toCharArray();   //證書密碼

     char keyPassword[]="12345678".toCharArray();   //證書別稱所使用的主要密碼

     KeyStore ks=KeyStore.getInstance("JKS");   //建立JKS金鑰庫

     ks.load(new FileInputStream(key),keyStorePass);

     //建立管理JKS金鑰庫的X.509金鑰管理器
     KeyManagerFactory kmf=KeyManagerFactory.getInstance("SunX509");

     kmf.init(ks,keyPassword);

     SSLContext sslContext=SSLContext.getInstance("SSLv3");

     sslContext.init(kmf.getKeyManagers(),null,null);
  
     //根據上面配置的SSL上下文來產生SSLServerSocketFactory,與通常的產生方法不同
     SSLServerSocketFactory factory=sslContext.getServerSocketFactory();

     s=(SSLServerSocket)factory.createServerSocket(thePort);

    }catch(Exception e)
    {
     System.out.println(e);
    }
    return(s);
   }
  
  
   public static void main(String args[])
   {
    try
    {
     server=getServerSocket(port);
     System.out.println("在”+port+”埠等待連線...");

     while(true)
     {
      SSLSocket socket=(SSLSocket)server.accept();
     
      //將得到的socket交給CreateThread物件處理,主執行緒繼續監聽
      new CreateThread(socket);
     
     }
    }catch(Exception e)
    {
     System.out.println("main方法錯誤80:"+e);
    }
   }
}

/*
*內部類,獲得主執行緒的socket連線,生成子執行緒來處理
*/

class CreateThread extends Thread
{
   static BufferedReader in;
   static PrintWriter out;
   static Socket s;
  
   /*
   *建構函式,獲得socket連線,初始化in和out物件
   */
  
   public CreateThread(Socket socket)
   {
    try
    {
     s=socket;
     in=new BufferedReader(new InputStreamReader(s.getInputStream(),"gb2312"));

     out=new PrintWriter(s.getOutputStream(),true);

     start();   //開新執行緒執行run方法

    }catch(Exception e)
    {
     System.out.println(e);
    }
   
   }
  
   /*
   *執行緒方法,處理socket傳遞過來的資料
   */
  
   public void run()
   {
    try
    {
     String msg=in.readLine();
     System.out.println(msg);
     s.close();
    }catch(Exception e)
    {
     System.out.println(e);
    }
   }
}

將我們剛才生成的證書放到程式所在的目錄下,上面的程式碼就可以在編譯之後執行:

java org.ec107.ssl.SSLServer

在8266埠等待連線…

三、 客戶端

客戶端的程式碼相對簡單,我們可以不在程式中指定SSL環境,而是在執行客戶端程式時指定。需要注意的是客戶端並沒有匯入證書,而是採用了預設的工廠方法構造SSLSocket:

● SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();

構造預設的工廠方法

●Socket s=factory.createSocket("localhost",port);

開啟一個SSLSocket連線

/*
*SSL Socket 的客戶端
*@Author Bromon
*/

package org.ec107.ssl;

import java.net.*;
import javax.net.ssl.*;
import javax.net.*;
import java.io.*;

public class SSLClient
{
   static int port=8266;
   public static void main(String args[])
   {
    try
    {
     SSLSocketFactory factory=(SSLSocketFactory)SSLSocketFactory.getDefault();

     Socket s=factory.createSocket("localhost",port);
    
     PrintWriter out=new PrintWriter(s.getOutputStream(),true);
     out.println("安全的說你好");
     out.close();
     s.close();
    }catch(Exception e)
    {
     System.out.println(e);
    }
   }
}

把伺服器產生的證書(SSLKey)拷貝到程式所在的目錄,執行這個程式的時候需要向javax.net.ssl.trustStore環境變數傳入證書名:

java –Djavax.net.ssl.trustStore=SSLKey org.ec107.ssl.SSLClient

可以在伺服器的控制檯看到客戶端傳送過來的資料。

執行客戶端可以有另一種方法,把證書拷貝到java home/lib/security目錄下,名字改為jssecacerts,然後可以直接執行客戶端:

java org.ec107.ssl.SSLClient

程式會自動的到上述目錄下去尋找jssecacerts檔案作為預設的證書。需要注意的是這裡的java home並不是我們在安裝J2SE時指定的那個JAVA_HOME。可以執行一個程式來得到java home的位置:

public class GetJavaHome
{
    public static void main(String args[])
    {
      System.out.println(System.getProperty(“java.home”));
    }
}

一般情況下(windows 2K)hava home的位置是在C:Program FilesJavaj2re1.4.0_02,相對的,證書就應該拷貝到C:Program FilesJavaj2re1.4.0_02libsecurity下,如果安裝了自帶JDK的Java IDE,比如JBuilder,情況可能會有不同。

    如果程式客戶在不持有證書的情況下直接進行連線,伺服器端會產生執行時異常,不允許進行連線。

執行環境:windows 2K server,j2sdk1.4.1