http://blog.163.com/wanjiangwei420@126/blog/static/121785112201081022634263/
?
马来西亚项目中,局方要求采用https 访问webservice,所以开发了一个 HelloWorld ,供项目组成员参考。
本地环境:tomcat6.0 + jdk1.6
服务器环境:websphere6.1+IHS+plugins+jdk1.5
在配置中也遇到过一些问题,将在最后做总结。
?
一、数字证书准备?
原文出去:http://han-zw.javaeye.com/blog/640737
在本地配置的时候,路径不同,注意修改过来
?
关于数字证书部分我是用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对应其密码
?
四、测试
启动tomcat6.0,输入:https://localhost:8443/HelloWorld/services/HelloWorldService?wsdl
这里要注意端口,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());
//上面的语句一定要加上,否则会报错
???/** 本机环境 */
???String wsdlUrl = "https://192.168.16.119:8443/HelloWorld/services/HelloWorldService";
?? String nameSpaceUri = "https://192.168.16.119:8443/HelloWorld/services/HelloWorldService";
?
// 创建调用对象 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服务器进程,证书已经替换上去了
?
七、获取服务器证书
参考:http://lyh7609.javaeye.com/blog/509064
代码如下:
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!