1. 程式人生 > >通過 https 連線webservice示例

通過 https 連線webservice示例

馬來西亞專案中,局方要求採用https 訪問webservice,所以開發了一個 HelloWorld ,供專案組成員參考。

本地環境:tomcat6.0 + jdk1.6

伺服器環境:websphere6.1+IHS+plugins+jdk1.5

在配置中也遇到過一些問題,將在最後做總結。

一、數字證書準備 

在本地配置的時候,路徑不同,注意修改過來

 
關於數字證書部分我是用openssl做的,也是個開源的軟體,前不久剛剛釋出了1.0版本(做了11年才正式釋出,由衷的佩服,老外真是有股哏勁)。網上很多文章介紹用java自帶的keytool命令完成,我沒有試過,不過看文章介紹好像keytool沒有CA認證的功能。下面開始數字證書相關操作。


1. 下載、安裝openssl(好像是廢話)
Openssl建議大家用1.0版本,畢竟是正式版本。我用的時候正式版還沒出來,當時用的是OpenSSL 0.9.8m,不要用OpenSSL 0.9.8h這個版本(有個bug,會影響到後面的操作)。安裝後從命令列進入安裝目錄下的bin目錄。Ready! GO!。


2.建立CA的私鑰
執行以下命令openssl genrsa -des3 -out ../demo/ca/ca.key 1024
demo是我的工作目錄,接下來會提示你輸入密碼,後面用到的密碼會很多,最好都認真記下來。


3.建立CA證書
openssl req -new -x509 -key ../demo/ca/ca.key -out ../demo/ca/ca.crt -days 365
x509是一種加密的標準,-out是指輸出的檔案路徑,-key是指定私鑰,也就是上一步生成的那個,-days是指證書有效期。
注:再輸入common name時你可以指定你自己的名字,但是不能輸入你的伺服器名(www.XX.X.com)

4.建立server端的私鑰
因為咱們是要在server端提供SSL的webservice,所以在server端需要使用私鑰庫和信任庫。
openssl genrsa -des3 -out ../demo/server/server.key 1024

5.建立server證書籤名請求
我們可以傳送簽名請求到一個官方的CA機構,這些機構都是要收費的,而且還要嚴格稽核,至於我們自己開發過程中的話實在是沒必要。我們直接傳送到我們剛才通過openssl構建的CA就可以了。
openssl req -new -key ../demo/server/server.key -out ../demo/server/server.csr
注意這裡的common name,此處填寫你的伺服器的ip或者域名,例如localhost,也就是你要為哪臺伺服器做證書就指定那臺機器。

6.CA簽署server證書
如果是第一次通過CA簽署證書的話,執行如下命令
openssl x509 -req -days 30 -in ../demo/server/server.csr -CA ../demo/ca/ca.crt -CAkey ../demo/ca/ca.key -CAcreateserial -out ../demo/server/server.crt
其中的-CAcreateserial是指建立一個新的序列檔案。這樣openssl會在當前目錄下建立一個名為ca.srl的檔案儲存序列號(官方是這樣說的,我本地產生的序列檔案是.srl,搞不清怎麼回事,可能是建立時沒指定名字吧,不過不影響後面的操作)。下次再次簽署證書時就可以直接指定這個序列檔案了。命令如下:openssl x509 -req -days 30 -in ../demo/server/server.csr -CA ../demo/ca/ca.crt -CAkey ../demo/ca/ca.key -CAserial .srl -out ../demo/server/server.crt
輸入CA私鑰的密碼後簽署成功。

7.建立server端的pkcs12檔案
openssl pkcs12 -export -in ../demo/server/server.crt -inkey ../demo/server/server.key -out ../demo/server/server.p12 -name demo_server
注意其中的-name demo_server,這個是指定keystore的別名,記下來,很重要哦(weblogic要用到,網上的資料都沒有這個引數,害得我weblogic配置時費老了勁了)。

8.轉換pkcs12為JKS keystore檔案
這個過程需要用到jetty.jar,下載相應jar後新增到classpath,然後執行如下命令
java org.mortbay.util.PKCS12Import ../demo/server/server.p12 ../demo/server/server.jks

如果環境變數沒有配置好,執行上面語句報錯,可以執行本段程式碼:

public class MyPKCS12Import {

 public static void main(String[] args) throws Exception {
  File fileOut;
  // if (args.length < 1) {
  // System.err
  // .println("usage: java PKCS12Import {pkcs12file} [newjksfile]");
  //
  // System.exit(1);
  // }
  //
  // File fileIn = new File(args[0]);
  //
  // if (args.length > 1)
  // fileOut = new File(args[1]);
  // else {
  // fileOut = new File("newstore.jks");
  // }

  File fileIn = new File("E:/openssl/openssl-0.9.8k_WIN32/bin/ssl.p12");

  fileOut = new File("E:/openssl/openssl-0.9.8k_WIN32/bin/ssl.jks");

  if (!(fileIn.canRead())) {
   System.err.println("Unable to access input keystore: "
     + fileIn.getPath());

   System.exit(2);
  }

  if ((fileOut.exists()) && (!(fileOut.canWrite()))) {
   System.err.println("Output file is not writable: "
     + fileOut.getPath());

   System.exit(2);
  }

  KeyStore kspkcs12 = KeyStore.getInstance("pkcs12");
  KeyStore ksjks = KeyStore.getInstance("jks");

  LineNumberReader in = new LineNumberReader(new InputStreamReader(
    System.in));
  System.out.print("Enter input keystore passphrase: ");
  char[] inphrase = in.readLine().toCharArray();
  System.out.print("Enter output keystore passphrase: ");
  char[] outphrase = in.readLine().toCharArray();

  kspkcs12.load(new FileInputStream(fileIn), inphrase);

  ksjks.load((fileOut.exists()) ? new FileInputStream(fileOut) : null,
    outphrase);

  Enumeration eAliases = kspkcs12.aliases();
  int n = 0;
  while (eAliases.hasMoreElements()) {
   String strAlias = (String) eAliases.nextElement();
   System.err.println("Alias " + (n++) + ": " + strAlias);

   if (kspkcs12.isKeyEntry(strAlias)) {
    System.err.println("Adding key for alias " + strAlias);
    Key key = kspkcs12.getKey(strAlias, inphrase);

    Certificate[] chain = kspkcs12.getCertificateChain(strAlias);

    ksjks.setKeyEntry(strAlias, key, outphrase, chain);
   }
  }

  OutputStream out = new FileOutputStream(fileOut);
  ksjks.store(out, outphrase);
  out.close();
 }

 static void dumpChain(Certificate[] chain) {
  for (int i = 0; i < chain.length; ++i) {
   Certificate cert = chain[i];
   if (cert instanceof X509Certificate) {
    X509Certificate x509 = (X509Certificate) chain[i];
    System.err.println("subject: " + x509.getSubjectDN());
    System.err.println("issuer: " + x509.getIssuerDN());
   }
  }
 }

}


在此處輸入上一步設定到export password。
Server端相關檔案就完成了,現在可以用java的keytool命令檢視一下生成的server.jks的內容
keytool -v -list -keystore ../demo/server/server.jks
接下來開始準備client端的相關檔案,因為我們啟用了數字證書的機制,client在通過webservice訪問server時也需要提供自己的證書,也就是server和client相互認證(客戶要求的,唉)。客戶端的相關操作與server端類似,不做過多說明。

9.建立client端的私鑰
openssl req -new -newkey rsa:1024 -nodes  -out ../demo/client/client.req -keyout ../demo/client/client.key

10.建立client端證書籤名請求
openssl x509 -CA ../demo/ca/ca.crt -CAkey ../demo/ca/ca.key -CAserial .srl -req -in ../demo/client/client.req -out ../demo/client/client.pem -days 365

11.建立client端的pkcs12檔案
openssl pkcs12 -export -clcerts -in ../demo/client/client.pem -inkey ../demo/client/client.key -out ../demo/client/client.p12 -name

12.建立client端的jks檔案
java org.mortbay.util.PKCS12Import ../demo/client/client.p12 ../demo/client/client.jks

13.建立信任金鑰庫
這次用到java的keytool命令
keytool -genkey -alias dummy -keyalg RSA -keystore ../demo/server/truststore.jks
到此為止數字證書的部分就完成了,下面介紹一下tomcat如何配置ssl支援。

14.將CA認證過的證書匯入信任庫

keytool -import -v -trustcacerts -alias my_ca -file ../demo/ca/ca.crt -keystore ../demo/server/truststore.jks

通過下面的命令可以檢視信任庫的詳細資訊

keytool -v -list -keystore ../demo/server/truststore.jks

二、釋出 webservice

釋出一個最簡單的webservice(這裡不做說明,可以參考我其他博文)

三、web容器配置

1、tomcat

把tomcat6中server.xml中https的connector放開,配置如下
<Connector  port="8443" maxHttpHeaderSize="8192" 
SSLEnabled="true"
         maxThreads="150" 
         minSpareThreads="25" 
         maxSpareThreads="75" 
         enableLookups="false" 
         disableUploadTimeout="true" 
         acceptCount="100" 
         scheme="https" 
         secure="true" 
         clientAuth="false" 
         sslProtocol="TLS" 
         keystoreFile="/conf/server.jks" 
         keystorePass="XXXXXX" 
         algorithm="SunX509" 
     /> 
注:keystoreFile對應server端的jks檔案,keystorePass對應其密碼

四、測試

這裡要注意埠,http下用的是80埠,而https下用的是443埠。

五、客戶端測試程式:

public static void main(String[] args) {
  try {
   System.out.println(">>>getMessage");
   

   //ssl.jks是生成的金鑰
   //System.setProperty("javax.net.ssl.trustStore", "E:/openssl/openssl-0.9.8k_WIN32/bin/ssl.jks");
   
  // System.setProperty("javax.net.ssl.trustStore", "E:/JAVA/java/jre/lib/security/jssecacerts");

   System.setProperty("javax.net.ssl.trustStorePassword","changeit");

   //Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());

//上面的語句一定要加上,否則會報錯

// 建立呼叫物件 b
   Service service = new Service();
   Call call = null;
   call = (Call) service.createCall();

   // 呼叫sayHello
   String thistime = java.text.DateFormat.getDateTimeInstance()
     .format(new java.util.Date());
   System.out.println(">>>QUERYSTAFF---begin------------" + thistime);
   
call.setOperationName(new QName(nameSpaceUri, "sayHello"));
   call.setTargetEndpointAddress(wsdlUrl);   
   Object ret = call.invoke(new Object[]{"suwan"});
   System.out.println(ret.toString());
   
   thistime = java.text.DateFormat.getDateTimeInstance().format(
     new java.util.Date());
   System.out.println(">>>QUERYSTAFF---end------------"
     + java.text.DateFormat.getDateTimeInstance().format(
       new java.util.Date()));
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

六、WAS6.1下測試

以上程式都是在本機進行測試,客戶端和服務端都在同一臺機器上,下面就客戶端和服務端不再一同機器上的情況進行測試,生產上基本上都是這種情況

將步驟一中生產的aaa.jks檔案拷貝到was伺服器下,然後在WAS6.1下配置證書,如下:

將合成好的JKS檔案匯入到伺服器上

開啟"管理控制檯",輸入管理帳戶,點選“登入”,

在“安全性”下,點選“SSL證書和金鑰管理”

點選“管理端點安全配置”

在“本地拓撲”下的“入站”下,選擇“Server1”

在螢幕右側點選“金鑰庫和證書”

點選“NodeDefaultKeyStore”

點選“個人證書”

點選“匯入”

輸入JKS檔案的位置: “c:\ssl.jks”,型別選擇“JKS”,輸入保護密碼,然後點選“獲取金鑰檔案別名”

WAS會從JKS檔案中讀取金鑰對的別名,選擇JKS中的金鑰對別名,並輸入匯入到WAS後的別名,然後點選“確定”

這時可以看到,WAS中多了一個SSL別名的金鑰對,然後選中“default”別名,點選“替換”

選擇“替換為‘SSL’”,點選“確定”

重新啟動WAS伺服器程序,證書已經替換上去了

七、獲取伺服器證書

程式碼如下:

import java.io.*;
import java.net.URL;

import java.security.*;
import java.security.cert.*;

import javax.net.ssl.*;

public class InstallCert {

    public static void main(String[] args) throws Exception {
 String host = "192.168.2.77";
 int port=443;
 char[] passphrase = "changeit".toCharArray();
 
 /*
 if ((args.length == 1) || (args.length == 2)) {
     String[] c = args[0].split(":");
     host = c[0];
     port = (c.length == 1) ? 443 : Integer.parseInt(c[1]);
     String p = (args.length == 1) ? "changeit" : args[1];
     passphrase = p.toCharArray();
 } else {
     System.out.println("Usage: java InstallCert <host>[:port] [passphrase]");
     return;
 }*/

 File file = new File("E:/JAVA/java/jre/lib/security/cacerts");
 if (file.isFile() == false) {
     char SEP = File.separatorChar;
     File dir = new File(System.getProperty("java.home") + SEP
      + "lib" + SEP + "security");
     file = new File(dir, "jssecacerts");
     if (file.isFile() == false) {
  file = new File(dir, "cacerts");
     }
 }
 System.out.println("Loading KeyStore " + file + "...");
 InputStream in = new FileInputStream(file);
 KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
 ks.load(in, passphrase);
 in.close();

 SSLContext context = SSLContext.getInstance("TLS");
 TrustManagerFactory tmf =
     TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
 tmf.init(ks);
 X509TrustManager defaultTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
 SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
 context.init(null, new TrustManager[] {tm}, null);
 SSLSocketFactory factory = context.getSocketFactory();

 System.out.println("Opening connection to " + host + ":" + port + "...");
 SSLSocket socket = (SSLSocket)factory.createSocket(host, port);
 socket.setSoTimeout(10000);
 try {
     System.out.println("Starting SSL handshake...");
     socket.startHandshake();
     socket.close();
     System.out.println();
     System.out.println("No errors, certificate is already trusted");
 } catch (SSLException e) {
     System.out.println();
     e.printStackTrace(System.out);
 }

 X509Certificate[] chain = tm.chain;
 if (chain == null) {
     System.out.println("Could not obtain server certificate chain");
     return;
 }

 BufferedReader reader =
  new BufferedReader(new InputStreamReader(System.in));

 System.out.println();
 System.out.println("Server sent " + chain.length + " certificate(s):");
 System.out.println();
 MessageDigest sha1 = MessageDigest.getInstance("SHA1");
 MessageDigest md5 = MessageDigest.getInstance("MD5");
 for (int i = 0; i < chain.length; i++) {
     X509Certificate cert = chain[i];
     System.out.println
      (" " + (i + 1) + " Subject " + cert.getSubjectDN());
     System.out.println("   Issuer  " + cert.getIssuerDN());
     sha1.update(cert.getEncoded());
     System.out.println("   sha1    " + toHexString(sha1.digest()));
     md5.update(cert.getEncoded());
     System.out.println("   md5     " + toHexString(md5.digest()));
     System.out.println();
 }

 System.out.println("Enter certificate to add to trusted keystore or 'q' to quit: [1]");
 String line = reader.readLine().trim();
 int k;
 try {
     k = (line.length() == 0) ? 0 : Integer.parseInt(line) - 1;
 } catch (NumberFormatException e) {
     System.out.println("KeyStore not changed");
     return;
 }

 X509Certificate cert = chain[k];
 String alias = host + "-" + (k + 1);
 ks.setCertificateEntry(alias, cert);

 OutputStream out = new FileOutputStream("E:/JAVA/java/jre/lib/security/jssecacerts");
 ks.store(out, passphrase);
 out.close();

 System.out.println();
 System.out.println(cert);
 System.out.println();
 System.out.println
  ("Added certificate to keystore 'jssecacerts' using alias '"
  + alias + "'");
    }

    private static final char[] HEXDIGITS = "0123456789abcdef".toCharArray();

    private static String toHexString(byte[] bytes) {
 StringBuilder sb = new StringBuilder(bytes.length * 3);
 for (int b : bytes) {
     b &= 0xff;
     sb.append(HEXDIGITS[b >> 4]);
     sb.append(HEXDIGITS[b & 15]);
     sb.append(' ');
 }
 return sb.toString();
    }

    private static class SavingTrustManager implements X509TrustManager {

 private final X509TrustManager tm;
 private X509Certificate[] chain;

 SavingTrustManager(X509TrustManager tm) {
     this.tm = tm;
 }

 public X509Certificate[] getAcceptedIssuers() {
     throw new UnsupportedOperationException();
 }

 public void checkClientTrusted(X509Certificate[] chain, String authType)
  throws CertificateException {
     throw new UnsupportedOperationException();
 }

 public void checkServerTrusted(X509Certificate[] chain, String authType)
  throws CertificateException {
     this.chain = chain;
     tm.checkServerTrusted(chain, authType);
 }
    }

}

 執行上面程式碼之後,會在本地jdk下產生一個jssecacerts的證書檔案

八、修改客戶端測試程式碼

//生成的伺服器端證書目錄 

System.setProperty("javax.net.ssl.trustStore", "E:/JAVA/java/jre/lib/security/jssecacerts");

//證書密碼,changeit是預設的密碼   

System.setProperty("javax.net.ssl.trustStorePassword","changeit");

九、測試結果

>>>getMessage
>>>QUERYSTAFF---begin------------2010-9-10 14:14:40
 hello: suwan
>>>QUERYSTAFF---end------------2010-9-10 14:14:41

OK!