fix TomcatEngine to support https

This commit is contained in:
Looly 2024-12-24 23:25:57 +08:00
parent b4eb357775
commit cacfbd9fc8
13 changed files with 252 additions and 32 deletions

View File

@ -70,6 +70,26 @@ public class KeyManagerUtil {
} }
} }
/**
* 从KeyStore中获取{@link KeyManagerFactory}
*
* @param keyStore KeyStore
* @param password 密码
* @param algorithm 算法{@code null}表示默认算法如SunX509
* @param provider 算法提供者{@code null}使用JDK默认
* @return {@link KeyManager}列表
*/
public static KeyManagerFactory getKeyManagerFactory(final KeyStore keyStore, final char[] password,
final String algorithm, final Provider provider) {
final KeyManagerFactory keyManagerFactory = getKeyManagerFactory(algorithm, provider);
try {
keyManagerFactory.init(keyStore, password);
} catch (final KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
throw new HutoolException(e);
}
return keyManagerFactory;
}
/** /**
* 从KeyStore中获取{@link KeyManager}列表 * 从KeyStore中获取{@link KeyManager}列表
* *
@ -77,7 +97,7 @@ public class KeyManagerUtil {
* @param password 密码 * @param password 密码
* @return {@link KeyManager}列表 * @return {@link KeyManager}列表
*/ */
public static KeyManager[] getDefaultKeyManagers(final KeyStore keyStore, final char[] password) { public static KeyManager[] getKeyManagers(final KeyStore keyStore, final char[] password) {
return getKeyManagers(keyStore, password, null, null); return getKeyManagers(keyStore, password, null, null);
} }
@ -90,13 +110,8 @@ public class KeyManagerUtil {
* @param provider 算法提供者{@code null}使用JDK默认 * @param provider 算法提供者{@code null}使用JDK默认
* @return {@link KeyManager}列表 * @return {@link KeyManager}列表
*/ */
public static KeyManager[] getKeyManagers(final KeyStore keyStore, final char[] password, final String algorithm, final Provider provider) { public static KeyManager[] getKeyManagers(final KeyStore keyStore, final char[] password,
final KeyManagerFactory keyManagerFactory = getKeyManagerFactory(algorithm, provider); final String algorithm, final Provider provider) {
try { return getKeyManagerFactory(keyStore, password, algorithm, provider).getKeyManagers();
keyManagerFactory.init(keyStore, password);
} catch (final KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
throw new HutoolException(e);
}
return keyManagerFactory.getKeyManagers();
} }
} }

View File

@ -24,10 +24,7 @@ import org.dromara.hutool.core.text.StrUtil;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import java.security.GeneralSecurityException; import java.security.*;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/** /**
@ -51,6 +48,7 @@ public class SSLContextBuilder implements SSLProtocols, Builder<SSLContext> {
private KeyManager[] keyManagers; private KeyManager[] keyManagers;
private TrustManager[] trustManagers; private TrustManager[] trustManagers;
private SecureRandom secureRandom; private SecureRandom secureRandom;
private Provider provider;
/** /**
@ -114,6 +112,17 @@ public class SSLContextBuilder implements SSLProtocols, Builder<SSLContext> {
return this; return this;
} }
/**
* 设置 Provider
*
* @param provider Provider{@code null}表示使用默认或全局Provider
* @return this
*/
public SSLContextBuilder setProvider(final Provider provider) {
this.provider = provider;
return this;
}
/** /**
* 构建{@link SSLContext} * 构建{@link SSLContext}
* *
@ -133,7 +142,8 @@ public class SSLContextBuilder implements SSLProtocols, Builder<SSLContext> {
* @since 5.7.22 * @since 5.7.22
*/ */
public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException { public SSLContext buildChecked() throws NoSuchAlgorithmException, KeyManagementException {
final SSLContext sslContext = SSLContext.getInstance(protocol); final SSLContext sslContext = null != this.provider ?
SSLContext.getInstance(protocol, provider) : SSLContext.getInstance(protocol);
sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom); sslContext.init(this.keyManagers, this.trustManagers, this.secureRandom);
return sslContext; return sslContext;
} }

View File

@ -22,6 +22,7 @@ import org.dromara.hutool.core.io.IORuntimeException;
import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
/** /**
@ -88,6 +89,33 @@ public class SSLContextUtil {
trustManager == null ? null : new TrustManager[]{trustManager}); trustManager == null ? null : new TrustManager[]{trustManager});
} }
/**
* 创建和初始化{@link SSLContext}
*
* @param keyStore KeyStore
* @param password 密码
* @return {@link SSLContext}
* @throws IORuntimeException 包装 GeneralSecurityException异常
*/
public static SSLContext createSSLContext(final KeyStore keyStore, final char[] password) throws IORuntimeException {
return createSSLContext(
KeyManagerUtil.getKeyManagers(keyStore, password),
TrustManagerUtil.getTrustManagers(keyStore)
);
}
/**
* 创建和初始化{@link SSLContext}
*
* @param keyManagers 密钥管理器,{@code null}表示默认
* @param trustManagers 信任管理器, {@code null}表示默认
* @return {@link SSLContext}
* @throws IORuntimeException 包装 GeneralSecurityException异常
*/
public static SSLContext createSSLContext(final KeyManager[] keyManagers, final TrustManager[] trustManagers) throws IORuntimeException {
return createSSLContext(null, keyManagers, trustManagers);
}
/** /**
* 创建和初始化{@link SSLContext} * 创建和初始化{@link SSLContext}
* *

View File

@ -75,7 +75,8 @@ public class TrustManagerUtil {
* @return {@link X509TrustManager} or {@code null} * @return {@link X509TrustManager} or {@code null}
* @since 6.0.0 * @since 6.0.0
*/ */
public static X509TrustManager getTrustManager(final KeyStore keyStore, final String algorithm, final Provider provider) { public static X509TrustManager getTrustManager(final KeyStore keyStore, final String algorithm,
final Provider provider) {
final TrustManager[] tms = getTrustManagers(keyStore, algorithm, provider); final TrustManager[] tms = getTrustManagers(keyStore, algorithm, provider);
for (final TrustManager tm : tms) { for (final TrustManager tm : tms) {
if (tm instanceof X509TrustManager) { if (tm instanceof X509TrustManager) {
@ -94,7 +95,19 @@ public class TrustManagerUtil {
* @since 6.0.0 * @since 6.0.0
*/ */
public static TrustManager[] getDefaultTrustManagers() { public static TrustManager[] getDefaultTrustManagers() {
return getTrustManagers(null, null, null); return getTrustManagers(null);
}
/**
* 获取指定的{@link TrustManager}<br>
* 此方法主要用于获取自签证书的{@link TrustManager}
*
* @param keyStore {@link KeyStore}
* @return {@link TrustManager} or {@code null}
* @since 6.0.0
*/
public static TrustManager[] getTrustManagers(final KeyStore keyStore) {
return getTrustManagers(keyStore, null, null);
} }
/** /**
@ -107,7 +120,22 @@ public class TrustManagerUtil {
* @return {@link TrustManager} or {@code null} * @return {@link TrustManager} or {@code null}
* @since 6.0.0 * @since 6.0.0
*/ */
public static TrustManager[] getTrustManagers(final KeyStore keyStore, String algorithm, final Provider provider) { public static TrustManager[] getTrustManagers(final KeyStore keyStore, final String algorithm,
final Provider provider) {
return getTrustManagerFactory(keyStore, algorithm, provider).getTrustManagers();
}
/**
* 获取指定的{@link TrustManagerFactory}
*
* @param keyStore {@link KeyStore}
* @param algorithm 算法名称"SunX509"{@code null}表示默认SunX509
* @param provider 算法提供者如bc{@code null}表示默认SunJSSE
* @return {@link TrustManager} or {@code null}
* @since 6.0.0
*/
public static TrustManagerFactory getTrustManagerFactory(final KeyStore keyStore, String algorithm,
final Provider provider) {
final TrustManagerFactory tmf; final TrustManagerFactory tmf;
if(StrUtil.isEmpty(algorithm)){ if(StrUtil.isEmpty(algorithm)){
@ -128,6 +156,6 @@ public class TrustManagerUtil {
throw new HutoolException(e); throw new HutoolException(e);
} }
return tmf.getTrustManagers(); return tmf;
} }
} }

View File

@ -17,7 +17,9 @@
package org.dromara.hutool.crypto; package org.dromara.hutool.crypto;
import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.IoUtil;
import org.dromara.hutool.core.io.file.FileNameUtil;
import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.crypto.provider.GlobalProviderFactory; import org.dromara.hutool.crypto.provider.GlobalProviderFactory;
import java.io.File; import java.io.File;
@ -35,11 +37,12 @@ import java.security.Provider;
public class KeyStoreUtil { public class KeyStoreUtil {
/** /**
* Java密钥库(Java Key StoreJKS)KEY_STORE * Java密钥库(Java Key StoreJKS)KEY_STOREJava 平台特有的密钥库格式<br>
* JKS 密钥库可以用 Java keytool 工具进行管理
*/ */
public static final String TYPE_JKS = "JKS"; public static final String TYPE_JKS = "JKS";
/** /**
* jceks * JCEKSJava Cryptography Extension Key Store
*/ */
public static final String TYPE_JCEKS = "jceks"; public static final String TYPE_JCEKS = "jceks";
/** /**
@ -100,6 +103,31 @@ public class KeyStoreUtil {
return readKeyStore(TYPE_PKCS12, in, password); return readKeyStore(TYPE_PKCS12, in, password);
} }
/**
* 读取KeyStore文件<br>
* KeyStore文件用于数字证书的密钥对保存<br>
* 证书类型根据扩展名自动判断规则如下
* <pre>
* .jks .keystore -> JKS
* .p12 .pfx等其它 -> PKCS12
* </pre>
*
* @param keyFile 证书文件
* @param password 密码null表示无密码
* @return {@link KeyStore}
* @since 6.0.0
*/
public static KeyStore readKeyStore(final File keyFile, final char[] password) {
final String suffix = FileNameUtil.getSuffix(keyFile);
final String type;
if(StrUtil.equalsIgnoreCase(suffix, "jks") || StrUtil.equalsIgnoreCase(suffix, "keystore")){
type = TYPE_JKS;
}else{
type = TYPE_PKCS12;
}
return readKeyStore(type, keyFile, password);
}
/** /**
* 读取KeyStore文件<br> * 读取KeyStore文件<br>
* KeyStore文件用于数字证书的密钥对保存<br> * KeyStore文件用于数字证书的密钥对保存<br>

View File

@ -30,6 +30,7 @@ import java.security.Provider;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/** /**
* 数字证书{@link Certificate}相关工具类 * 数字证书{@link Certificate}相关工具类
@ -141,4 +142,35 @@ public class CertUtil {
} }
return factory; return factory;
} }
/**
* 判断一个证书是否是自签名的即证书由自己签发
* @param cert 证书
* @return true表示自签名的false表示非自签名的
*/
public static boolean isSelfSigned(final X509Certificate cert) {
return isSignedBy(cert, cert);
}
/**
* 验证一个证书是否由另一个证书签发<br>
* 来自sun.security.tools.KeyStoreUtil
*
* @param end 需要验证的终端证书
* @param ca 用于验证的CA证书
* @return 如果终端证书由CA证书签发则返回true否则返回false
*/
public static boolean isSignedBy(final X509Certificate end, final X509Certificate ca) {
// 检查CA证书的主题和终端证书的颁发者是否相同
if (!ca.getSubjectX500Principal().equals(end.getIssuerX500Principal())) {
return false;
}
try {
// 使用CA证书的公钥验证终端证书
end.verify(ca.getPublicKey());
return true;
} catch (final Exception e) {
return false;
}
}
} }

View File

@ -56,6 +56,12 @@
<artifactId>hutool-log</artifactId> <artifactId>hutool-log</artifactId>
<version>${project.parent.version}</version> <version>${project.parent.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>${project.parent.version}</version>
<scope>test</scope>
</dependency>
<!-- webservice SOAP 从javaEE变成jakartaEEjavax.xml.soap Jakarta XML SOAP(jakarta.xml.soap) --> <!-- webservice SOAP 从javaEE变成jakartaEEjavax.xml.soap Jakarta XML SOAP(jakarta.xml.soap) -->
<dependency> <dependency>
<groupId>jakarta.xml.soap</groupId> <groupId>jakarta.xml.soap</groupId>

View File

@ -16,7 +16,10 @@
package org.dromara.hutool.http.server; package org.dromara.hutool.http.server;
import org.dromara.hutool.core.net.ssl.SSLContextUtil;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import java.security.KeyStore;
/** /**
* 服务器配置 * 服务器配置
@ -115,6 +118,18 @@ public class ServerConfig {
return sslContext; return sslContext;
} }
/**
* 设置证书库<br>
* 此方法和{@link #setSslContext(SSLContext)}互斥
*
* @param keyStore 证书库
* @param passwd 密码
* @return this
*/
public ServerConfig setKeystore(final KeyStore keyStore, final char[] passwd) {
return setSslContext(SSLContextUtil.createSSLContext(keyStore, passwd));
}
/** /**
* 设置SSL上下文 * 设置SSL上下文
* *

View File

@ -125,20 +125,34 @@ public class TomcatEngine extends AbstractServerEngine {
// SSL配置 // SSL配置
final SSLContext sslContext = config.getSslContext(); final SSLContext sslContext = config.getSslContext();
if(null != sslContext){ if(null != sslContext){
final SSLHostConfig sslHostConfig = new SSLHostConfig(); protocol.setSSLEnabled(true);
final SSLHostConfigCertificate sslHostConfigCertificate = protocol.setSecure(true);
new SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.RSA); protocol.addSslHostConfig(createSSLHostConfig(sslContext));
sslHostConfigCertificate.setSslContext(new JSSESSLContext(sslContext));
sslHostConfig.addCertificate(sslHostConfigCertificate);
connector.addSslHostConfig(sslHostConfig);
connector.setScheme("https"); connector.setScheme("https");
connector.setSecure(true);
connector.setPort(config.getPort()); connector.setPort(config.getPort());
} }
return connector; return connector;
} }
/**
* 创建SSL HostConfig
*
* @param sslContext SSLContext
* @return SSL HostConfig
*/
private static SSLHostConfig createSSLHostConfig(final SSLContext sslContext) {
final SSLHostConfig sslHostConfig = new SSLHostConfig();
final SSLHostConfigCertificate sslHostConfigCertificate =
new SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.RSA);
sslHostConfigCertificate.setSslContext(new JSSESSLContext(sslContext));
sslHostConfig.addCertificate(sslHostConfigCertificate);
return sslHostConfig;
}
/** /**
* 初始化Context * 初始化Context
* *

View File

@ -1,12 +1,23 @@
package org.dromara.hutool.http.server.engine; package org.dromara.hutool.http.server.engine;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig; import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
public class JettyTest { public class JettyTest {
public static void main(final String[] args) { public static void main(final String[] args) {
final char[] pwd = "123456".toCharArray();
final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd);
// 初始化SSLContext
final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd);
final ServerEngine engine = ServerEngineFactory.createEngine("jetty"); final ServerEngine engine = ServerEngineFactory.createEngine("jetty");
engine.init(ServerConfig.of()); engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> { engine.setHandler((request, response) -> {
Console.log(request.getPath()); Console.log(request.getPath());
response.write("Hutool Jetty response test"); response.write("Hutool Jetty response test");

View File

@ -1,12 +1,23 @@
package org.dromara.hutool.http.server.engine; package org.dromara.hutool.http.server.engine;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig; import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
public class SunServerTest { public class SunServerTest {
public static void main(String[] args) { public static void main(String[] args) {
final char[] pwd = "123456".toCharArray();
final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd);
// 初始化SSLContext
final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd);
final ServerEngine engine = ServerEngineFactory.createEngine("SunHttpServer"); final ServerEngine engine = ServerEngineFactory.createEngine("SunHttpServer");
engine.init(ServerConfig.of()); engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> { engine.setHandler((request, response) -> {
Console.log(request.getPath()); Console.log(request.getPath());
response.write("Hutool Sun Server response test"); response.write("Hutool Sun Server response test");

View File

@ -1,12 +1,23 @@
package org.dromara.hutool.http.server.engine; package org.dromara.hutool.http.server.engine;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig; import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
public class TomcatTest { public class TomcatTest {
public static void main(String[] args) { public static void main(final String[] args) throws Exception {
final char[] pwd = "123456".toCharArray();
final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd);
// 初始化SSLContext
final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd);
final ServerEngine engine = ServerEngineFactory.createEngine("tomcat"); final ServerEngine engine = ServerEngineFactory.createEngine("tomcat");
engine.init(ServerConfig.of()); engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> { engine.setHandler((request, response) -> {
Console.log(request.getPath()); Console.log(request.getPath());
response.write("Hutool Tomcat response test"); response.write("Hutool Tomcat response test");

View File

@ -16,13 +16,24 @@
package org.dromara.hutool.http.server.engine; package org.dromara.hutool.http.server.engine;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Console; import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig; import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
public class UndertowTest { public class UndertowTest {
public static void main(String[] args) { public static void main(String[] args) {
final char[] pwd = "123456".toCharArray();
final KeyStore keyStore = KeyStoreUtil.readJKSKeyStore(FileUtil.file("d:/test/keystore.jks"), pwd);
// 初始化SSLContext
final SSLContext sslContext = SSLContextUtil.createSSLContext(keyStore, pwd);
final ServerEngine engine = ServerEngineFactory.createEngine("undertow"); final ServerEngine engine = ServerEngineFactory.createEngine("undertow");
engine.init(ServerConfig.of()); engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> { engine.setHandler((request, response) -> {
Console.log(request.getPath()); Console.log(request.getPath());
response.write("Hutool Undertow response test"); response.write("Hutool Undertow response test");