Tomcat SSL/TLS配置
SSL/TLS Configuration
概述
SSL/TLS是保护Web应用数据传输安全的关键技术。本文详细介绍Tomcat SSL/TLS的配置方法、证书管理、性能优化和故障排除技巧。
1. SSL基础知识与证书准备
1.1 生成自签名证书
#!/bin/bash
# generate-self-signed-cert.sh
KEYSTORE_FILE="conf/keystore.jks"
KEYSTORE_PASS="changeit"
KEY_ALIAS="tomcat"
VALIDITY_DAYS="365"
# 生成密钥库和自签名证书
keytool -genkeypair \
-alias $KEY_ALIAS \
-keyalg RSA \
-keysize 2048 \
-validity $VALIDITY_DAYS \
-keystore $KEYSTORE_FILE \
-storepass $KEYSTORE_PASS \
-keypass $KEYSTORE_PASS \
-dname "CN=localhost, OU=IT Department, O=Example Corp, L=City, ST=State, C=US"
echo "自签名证书已生成: $KEYSTORE_FILE"
1.2 使用Let's Encrypt证书
#!/bin/bash
# setup-letsencrypt.sh
DOMAIN="example.com"
EMAIL="admin@example.com"
WEBROOT="/opt/tomcat9/webapps/ROOT"
# 安装Certbot
sudo apt-get update
sudo apt-get install certbot
# 获取证书
sudo certbot certonly \
--webroot \
--webroot-path=$WEBROOT \
--email $EMAIL \
--agree-tos \
--no-eff-email \
-d $DOMAIN
# 转换为Java KeyStore格式
CERT_PATH="/etc/letsencrypt/live/$DOMAIN"
openssl pkcs12 -export \
-in "$CERT_PATH/fullchain.pem" \
-inkey "$CERT_PATH/privkey.pem" \
-out "/tmp/$DOMAIN.p12" \
-name tomcat \
-password pass:changeit
keytool -importkeystore \
-deststorepass changeit \
-destkeypass changeit \
-destkeystore "conf/$DOMAIN.jks" \
-srckeystore "/tmp/$DOMAIN.p12" \
-srcstoretype PKCS12 \
-srcstorepass changeit \
-alias tomcat
echo "Let's Encrypt证书已配置"
1.3 证书续期脚本
#!/bin/bash
# renew-letsencrypt.sh
DOMAIN="example.com"
CERT_PATH="/etc/letsencrypt/live/$DOMAIN"
KEYSTORE_PATH="/opt/tomcat9/conf/$DOMAIN.jks"
# 续期证书
sudo certbot renew --quiet
# 检查是否更新了证书
if [ "$CERT_PATH/fullchain.pem" -nt "$KEYSTORE_PATH" ]; then
echo "证书已更新,重新生成KeyStore..."
# 删除旧的KeyStore
rm -f "$KEYSTORE_PATH"
# 生成新的KeyStore
openssl pkcs12 -export \
-in "$CERT_PATH/fullchain.pem" \
-inkey "$CERT_PATH/privkey.pem" \
-out "/tmp/$DOMAIN.p12" \
-name tomcat \
-password pass:changeit
keytool -importkeystore \
-deststorepass changeit \
-destkeypass changeit \
-destkeystore "$KEYSTORE_PATH" \
-srckeystore "/tmp/$DOMAIN.p12" \
-srcstoretype PKCS12 \
-srcstorepass changeit \
-alias tomcat
# 重启Tomcat
systemctl restart tomcat
echo "证书已更新并重启Tomcat"
else
echo "证书未更新"
fi
2. 基础SSL连接器配置
2.1 简单SSL配置
<!-- server.xml - 基础SSL连接器 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150"
SSLEnabled="true"
scheme="https"
secure="true"
clientAuth="false"
sslProtocol="TLS">
<SSLHostConfig>
<Certificate certificateKeystoreFile="conf/keystore.jks"
certificateKeystorePassword="changeit"
type="RSA" />
</SSLHostConfig>
</Connector>
2.2 高安全性SSL配置
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="200"
SSLEnabled="true"
scheme="https"
secure="true">
<SSLHostConfig protocols="TLSv1.2,TLSv1.3"
ciphers="ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-CHACHA20-POLY1305,ECDHE-RSA-CHACHA20-POLY1305,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256"
honorCipherOrder="true"
disableCompression="true"
disableSessionTickets="false"
sessionTimeout="86400"
revocationEnabled="false">
<Certificate certificateKeystoreFile="conf/server.jks"
certificateKeystorePassword="serverpass"
certificateKeyAlias="server"
type="RSA" />
</SSLHostConfig>
</Connector>
3. 多域名SSL配置
3.1 SNI支持配置
<!-- 多域名SSL配置 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true">
<!-- 主域名配置 -->
<SSLHostConfig hostName="www.example.com"
protocols="TLSv1.2,TLSv1.3">
<Certificate certificateKeystoreFile="conf/www-example-com.jks"
certificateKeystorePassword="www123"
type="RSA" />
</SSLHostConfig>
<!-- API域名配置 -->
<SSLHostConfig hostName="api.example.com"
protocols="TLSv1.2,TLSv1.3">
<Certificate certificateKeystoreFile="conf/api-example-com.jks"
certificateKeystorePassword="api123"
type="RSA" />
</SSLHostConfig>
<!-- 通配符证书配置 -->
<SSLHostConfig hostName="*.example.com"
protocols="TLSv1.2,TLSv1.3">
<Certificate certificateKeystoreFile="conf/wildcard-example-com.jks"
certificateKeystorePassword="wild123"
type="RSA" />
</SSLHostConfig>
<!-- 默认配置 -->
<SSLHostConfig protocols="TLSv1.2,TLSv1.3">
<Certificate certificateKeystoreFile="conf/default.jks"
certificateKeystorePassword="default123"
type="RSA" />
</SSLHostConfig>
</Connector>
3.2 混合证书配置
<!-- RSA + ECC混合证书配置 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true">
<SSLHostConfig hostName="secure.example.com">
<!-- RSA证书 -->
<Certificate certificateKeystoreFile="conf/rsa-keystore.jks"
certificateKeystorePassword="rsa123"
certificateKeyAlias="rsa-key"
type="RSA" />
<!-- ECC证书 -->
<Certificate certificateKeystoreFile="conf/ecc-keystore.jks"
certificateKeystorePassword="ecc123"
certificateKeyAlias="ecc-key"
type="EC" />
</SSLHostConfig>
</Connector>
4. 客户端证书认证
4.1 双向SSL配置
<!-- 双向SSL认证配置 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true"
scheme="https"
secure="true">
<SSLHostConfig certificateVerification="required"
truststoreFile="conf/truststore.jks"
truststorePassword="trustpass"
truststoreType="JKS"
caCertificateFile="conf/ca-cert.pem"
revocationEnabled="true"
crlFile="conf/ca-crl.pem">
<Certificate certificateKeystoreFile="conf/server-keystore.jks"
certificateKeystorePassword="serverpass"
type="RSA" />
</SSLHostConfig>
</Connector>
4.2 可选客户端认证
<!-- 可选客户端证书认证 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true">
<SSLHostConfig certificateVerification="optional"
truststoreFile="conf/client-truststore.jks"
truststorePassword="clientpass">
<Certificate certificateKeystoreFile="conf/server.jks"
certificateKeystorePassword="serverpass"
type="RSA" />
</SSLHostConfig>
</Connector>
4.3 客户端证书验证Valve
// ClientCertificateValve.java
package com.example.ssl;
import org.apache.catalina.valves.ValveBase;
import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import javax.servlet.ServletException;
import java.io.IOException;
import java.security.cert.X509Certificate;
public class ClientCertificateValve extends ValveBase {
@Override
public void invoke(Request request, Response response)
throws IOException, ServletException {
// 获取客户端证书
X509Certificate[] certs = (X509Certificate[])
request.getAttribute("javax.servlet.request.X509Certificate");
if (certs != null && certs.length > 0) {
X509Certificate clientCert = certs[0];
// 验证证书
if (isValidClientCertificate(clientCert)) {
// 提取用户信息
String username = extractUsername(clientCert);
request.setAttribute("ssl.client.username", username);
// 继续处理请求
getNext().invoke(request, response);
} else {
response.sendError(403, "Invalid client certificate");
}
} else {
response.sendError(401, "Client certificate required");
}
}
private boolean isValidClientCertificate(X509Certificate cert) {
try {
// 检查证书有效期
cert.checkValidity();
// 检查证书颁发者
String issuer = cert.getIssuerDN().getName();
if (!issuer.contains("CN=Example CA")) {
return false;
}
// 检查证书用途
boolean[] keyUsage = cert.getKeyUsage();
if (keyUsage == null || !keyUsage[0]) { // Digital Signature
return false;
}
return true;
} catch (Exception e) {
return false;
}
}
private String extractUsername(X509Certificate cert) {
String subject = cert.getSubjectDN().getName();
// 提取CN值作为用户名
String[] parts = subject.split(",");
for (String part : parts) {
if (part.trim().startsWith("CN=")) {
return part.trim().substring(3);
}
}
return "unknown";
}
}
5. SSL性能优化
5.1 会话缓存优化
<!-- SSL会话缓存优化 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true">
<SSLHostConfig sessionCacheSize="10000"
sessionTimeout="86400"
disableSessionTickets="false">
<Certificate certificateKeystoreFile="conf/keystore.jks"
certificateKeystorePassword="changeit"
type="RSA" />
</SSLHostConfig>
</Connector>
5.2 OCSP Stapling配置
<!-- OCSP Stapling配置 -->
<Connector port="8443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true">
<SSLHostConfig certificateVerification="none"
revocationEnabled="true"
caCertificateFile="conf/ca-bundle.crt">
<Certificate certificateKeystoreFile="conf/server.jks"
certificateKeystorePassword="serverpass"
type="RSA" />
</SSLHostConfig>
</Connector>
5.3 SSL性能监控
// SSLPerformanceMonitor.java
package com.example.ssl;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
public class SSLPerformanceMonitor {
private MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
public void monitorSSLConnector() throws Exception {
ObjectName connectorName = new ObjectName(
"Catalina:type=Connector,port=8443"
);
// SSL连接统计
Long bytesReceived = (Long) mbs.getAttribute(connectorName, "bytesReceived");
Long bytesSent = (Long) mbs.getAttribute(connectorName, "bytesSent");
Integer requestCount = (Integer) mbs.getAttribute(connectorName, "requestCount");
Long processingTime = (Long) mbs.getAttribute(connectorName, "processingTime");
System.out.println("SSL连接器性能统计:");
System.out.println("接收字节数: " + formatBytes(bytesReceived));
System.out.println("发送字节数: " + formatBytes(bytesSent));
System.out.println("请求总数: " + requestCount);
System.out.println("平均处理时间: " + (processingTime / Math.max(requestCount, 1)) + "ms");
// SSL会话信息
monitorSSLSessions();
}
private void monitorSSLSessions() {
// 监控SSL会话缓存
try {
ObjectName sslName = new ObjectName(
"Catalina:type=SSLHostConfig,host=*"
);
// 获取SSL相关统计(如果可用)
System.out.println("SSL会话监控信息:");
System.out.println("会话缓存状态: 正常");
} catch (Exception e) {
System.out.println("SSL会话监控不可用: " + e.getMessage());
}
}
private String formatBytes(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.2f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.2f MB", bytes / (1024.0 * 1024));
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
}
}
6. SSL故障排除
6.1 SSL诊断脚本
#!/bin/bash
# ssl-diagnostics.sh
HOSTNAME="localhost"
PORT="8443"
KEYSTORE="/opt/tomcat9/conf/keystore.jks"
KEYSTORE_PASS="changeit"
echo "=== SSL/TLS诊断工具 ==="
# 1. 检查端口连通性
test_ssl_connection() {
echo "1. 测试SSL连接..."
if timeout 10 openssl s_client -connect $HOSTNAME:$PORT -servername $HOSTNAME < /dev/null 2>/dev/null; then
echo " ✓ SSL连接成功"
else
echo " ✗ SSL连接失败"
fi
}
# 2. 检查证书有效性
check_certificate() {
echo "2. 检查证书..."
# 检查KeyStore中的证书
if [ -f "$KEYSTORE" ]; then
echo " 检查KeyStore证书:"
keytool -list -keystore "$KEYSTORE" -storepass "$KEYSTORE_PASS" -v | \
grep -E "(Alias name|Valid from|until|Subject|Issuer)"
fi
# 检查服务器证书
echo " 检查服务器证书:"
echo | openssl s_client -connect $HOSTNAME:$PORT -servername $HOSTNAME 2>/dev/null | \
openssl x509 -noout -dates -subject -issuer 2>/dev/null
}
# 3. 检查SSL/TLS协议支持
check_protocols() {
echo "3. 检查支持的协议..."
protocols=("ssl3" "tls1" "tls1_1" "tls1_2" "tls1_3")
for protocol in "${protocols[@]}"; do
if echo | openssl s_client -connect $HOSTNAME:$PORT -$protocol 2>/dev/null | grep -q "Verify return code: 0"; then
echo " ✓ $protocol 支持"
else
echo " ✗ $protocol 不支持"
fi
done
}
# 4. 检查密码套件
check_ciphers() {
echo "4. 检查密码套件..."
echo | openssl s_client -connect $HOSTNAME:$PORT -cipher 'ALL' 2>/dev/null | \
grep "Cipher is" | head -5
}
# 5. SSL配置评分
ssl_security_check() {
echo "5. SSL安全评分..."
score=0
total=5
# 检查TLS 1.2支持
if echo | openssl s_client -connect $HOSTNAME:$PORT -tls1_2 2>/dev/null | grep -q "Protocol.*TLSv1.2"; then
echo " ✓ 支持TLS 1.2"
score=$((score + 1))
else
echo " ✗ 不支持TLS 1.2"
fi
# 检查TLS 1.3支持
if echo | openssl s_client -connect $HOSTNAME:$PORT -tls1_3 2>/dev/null | grep -q "Protocol.*TLSv1.3"; then
echo " ✓ 支持TLS 1.3"
score=$((score + 1))
else
echo " ✗ 不支持TLS 1.3"
fi
# 检查弱密码套件
if ! echo | openssl s_client -connect $HOSTNAME:$PORT -cipher 'LOW:EXP:NULL:aNULL' 2>/dev/null | grep -q "Cipher is"; then
echo " ✓ 无弱密码套件"
score=$((score + 1))
else
echo " ✗ 存在弱密码套件"
fi
# 检查证书强度
key_size=$(echo | openssl s_client -connect $HOSTNAME:$PORT 2>/dev/null | openssl x509 -noout -text | grep "Public-Key" | grep -o "[0-9]*")
if [ "$key_size" -ge 2048 ]; then
echo " ✓ 证书密钥强度足够 (${key_size}位)"
score=$((score + 1))
else
echo " ✗ 证书密钥强度不足 (${key_size}位)"
fi
# 检查HSTS
if curl -s -I https://$HOSTNAME:$PORT/ 2>/dev/null | grep -q "Strict-Transport-Security"; then
echo " ✓ 启用HSTS"
score=$((score + 1))
else
echo " ✗ 未启用HSTS"
fi
echo " SSL安全评分: $score/$total ($(( score * 100 / total ))%)"
}
# 执行所有检查
test_ssl_connection
echo
check_certificate
echo
check_protocols
echo
check_ciphers
echo
ssl_security_check
6.2 常见SSL问题解决
#!/bin/bash
# ssl-troubleshoot.sh
fix_common_ssl_issues() {
echo "=== SSL常见问题修复 ==="
# 1. 检查Java版本和SSL支持
echo "1. 检查Java SSL支持..."
java -Dcom.sun.net.ssl.checkRevocation=false \
-Dhttps.protocols=TLSv1.2,TLSv1.3 \
-version
# 2. 更新Java加密策略
echo "2. 检查加密策略..."
if [ -f "$JAVA_HOME/jre/lib/security/local_policy.jar" ]; then
echo " 发现限制性加密策略,建议更新为无限制策略"
fi
# 3. 检查系统时间
echo "3. 检查系统时间..."
date
echo " 确保系统时间准确,避免证书验证失败"
# 4. 清理SSL缓存
echo "4. 清理SSL会话缓存..."
find /tmp -name "hsperfdata_*" -exec rm -rf {} \; 2>/dev/null
# 5. 验证证书链
echo "5. 验证证书链..."
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt \
<(openssl s_client -connect localhost:8443 -showcerts 2>/dev/null | \
openssl x509) 2>/dev/null
}
# 执行修复
fix_common_ssl_issues
7. SSL配置最佳实践
7.1 生产环境SSL配置模板
<!-- 生产环境SSL配置模板 -->
<Connector port="443"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="300"
SSLEnabled="true"
scheme="https"
secure="true"
compression="on"
compressionMinSize="2048"
compressibleMimeType="text/html,text/xml,text/css,application/javascript,application/json">
<!-- 高安全性配置 -->
<SSLHostConfig protocols="TLSv1.2,TLSv1.3"
ciphers="ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
honorCipherOrder="true"
disableCompression="true"
sessionCacheSize="20000"
sessionTimeout="86400"
certificateVerification="none">
<Certificate certificateKeystoreFile="conf/production.jks"
certificateKeystorePassword="prodpass123"
certificateKeyAlias="production"
type="RSA" />
</SSLHostConfig>
<!-- 安全头设置 -->
<Valve className="org.apache.catalina.valves.HttpHeaderSecurityFilter"
hstsEnabled="true"
hstsMaxAgeSeconds="31536000"
hstsIncludeSubdomains="true"
hstsPreload="true" />
</Connector>
7.2 SSL监控脚本
#!/bin/bash
# ssl-monitor.sh
DOMAINS=("example.com" "api.example.com" "admin.example.com")
ALERT_DAYS=30
for domain in "${DOMAINS[@]}"; do
echo "检查域名: $domain"
# 获取证书到期时间
expiry_date=$(echo | openssl s_client -connect $domain:443 -servername $domain 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ ! -z "$expiry_date" ]; then
expiry_epoch=$(date -d "$expiry_date" +%s)
current_epoch=$(date +%s)
days_left=$(( (expiry_epoch - current_epoch) / 86400 ))
echo " 证书到期时间: $expiry_date"
echo " 剩余天数: $days_left"
if [ $days_left -lt $ALERT_DAYS ]; then
echo " ⚠️ 警告: 证书即将到期!"
fi
else
echo " ❌ 无法获取证书信息"
fi
echo
done
小结
通过本文学习,你应该掌握:
- SSL/TLS基础知识和证书管理
- Tomcat SSL连接器的详细配置
- 多域名和SNI支持配置
- 客户端证书认证配置
- SSL性能优化技巧
- SSL故障排除方法
- SSL安全配置最佳实践
- SSL监控和维护技术
下一篇文章将介绍Tomcat性能优化技术。