这两天研究了下Tomcat如何配置https,实现以https的方式访问我们的web service,下面就如何配置Tomcat以及在访问过程中出现的问题做个简单的总结。
首先说明下,我使用的Tomcat的版本是Version 7.0.22。
我们先找到Tomcat的官方参考文档中如何配置SSL:http://tomcat.apache.org/tomcat-7.0-doc/ssl-howto.html,里面介绍了如何配置SSL的步骤,总得来说,首先生成keystore文件,步骤如下:
1.在windows命令行下输入
2. 接下来提示输入密码,可以使用Tomcat的默认值changeit,然后按照提示输入相关信息后确认,这里的别名tomcat可以自己定义,一般情况下,上面生成的.keystore文件会在你的${user.home}目录下。
然后,找到Tomcat下的server.xml文件,找到
<-- Define a SSL Coyote HTTP/1.1 Connector on port 8443 --> <!-- <Connector port="8443" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" clientAuth="false" sslProtocol="TLS"/> -->
把打开注释,加上两个属性
keystoreFile="${user.home}/.keystore" keystorePass="changeit"
其中,keystoreFile是生成的服务证书的位置,keystorePass是密码,如果你没输入密码,默认的是changeit。代码如下:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
SSLEnabled="true"
maxThreads="200" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS"
keystoreFile="E:/apache-tomcat-7.0.22/.keystore"
keystorePass="123456"/>
到这里,我们的Tomcat部分就以配置完成了,当我把我的服务部署到Tomcat下面时,出现了下面的错误:
我们要想成功访问我们的web service还需下面的步骤。
上面的错误告诉我们没有找到验证路径,所以还要生成一个验证证书,我们只需要用下面的代码来帮助我们生成:
public class InstallCert {
public static void main(String[] args) throws Exception {
String host;
int port;
char[] passphrase;
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("jssecacerts");
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("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);
}
}
}
然后,运行这个程序,运行程序时,需要输入参数,在Run Configure的Argument输入localhost:8443,Run,显示以下信息:
Opening connection to localhost:8443...
Starting SSL handshake...
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1591)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:187)
at com.sun.net.ssl.internal.ssl.Handshaker.fatalSE(Handshaker.java:181)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1035)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:124)
at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:516)
at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:454)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:884)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1096)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1123)
at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1107)
at com.neusoft.mega.client.InstallCert.main(InstallCert.java:85)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:285)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:191)
at sun.security.validator.Validator.validate(Validator.java:218)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
at com.neusoft.mega.client.InstallCert$SavingTrustManager.checkServerTrusted(InstallCert.java:178)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1027)
... 8 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:280)
... 14 more
Server sent 1 certificate(s):
1 Subject CN=localhost, OU=It, O=It, L=CH, ST=CH, C=86
Issuer CN=localhost, OU=It, O=It, L=CH, ST=CH, C=86
sha1 e4 88 87 2f 9f 22 08 14 3a b7 85 62 99 0a 11 92 2c 8b 08 5c
md5 5f d5 f1 6c d4 28 fc 8e 56 88 45 fa 27 67 18 eb
Enter certificate to add to trusted keystore or 'q' to quit: [1]
在console下输入1,会输出一堆信息,最后出现一句:Added certificate to keystore 'jssecacerts' using alias 'localhost-1',说明我们的验证文件就生成了,这个文件会在我们的
%JAVA_HOME%/jre6/lib/security
下,文件名为jssecacerts。
到此为止,我们就可以正常通过https访问我们的web service了。
注意:
我开始第一次访问时,出现了:java.security.cert.CertificateException: No name matching localhost found错误,这个错误是说我们指定资源证书的CN与资源访问地址不匹配,在我们生成ketstore文件时,我们在输入您的名字和姓氏的名称与我们自愿的地址不一致。
(C) 版权所有 1985-2001 Microsoft Corp.
C:\Documents and Settings\user>keytool -genkey -alias tomcat -keyalg RSA
输入keystore密码:
您的名字与姓氏是什么?
[Unknown]:
localhost
比如说,而偶们的访问地址是https://localhost:8443/tomcat,那么我们资源证书的CN也为localhost。