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}列表
*
@ -77,7 +97,7 @@ public class KeyManagerUtil {
* @param password 密码
* @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);
}
@ -90,13 +110,8 @@ public class KeyManagerUtil {
* @param provider 算法提供者{@code null}使用JDK默认
* @return {@link KeyManager}列表
*/
public static KeyManager[] getKeyManagers(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.getKeyManagers();
public static KeyManager[] getKeyManagers(final KeyStore keyStore, final char[] password,
final String algorithm, final Provider provider) {
return getKeyManagerFactory(keyStore, password, algorithm, provider).getKeyManagers();
}
}

View File

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

View File

@ -22,6 +22,7 @@ import org.dromara.hutool.core.io.IORuntimeException;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
/**
@ -88,6 +89,33 @@ public class SSLContextUtil {
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}
*

View File

@ -75,7 +75,8 @@ public class TrustManagerUtil {
* @return {@link X509TrustManager} or {@code null}
* @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);
for (final TrustManager tm : tms) {
if (tm instanceof X509TrustManager) {
@ -94,7 +95,19 @@ public class TrustManagerUtil {
* @since 6.0.0
*/
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}
* @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;
if(StrUtil.isEmpty(algorithm)){
@ -128,6 +156,6 @@ public class TrustManagerUtil {
throw new HutoolException(e);
}
return tmf.getTrustManagers();
return tmf;
}
}

View File

@ -17,7 +17,9 @@
package org.dromara.hutool.crypto;
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.text.StrUtil;
import org.dromara.hutool.crypto.provider.GlobalProviderFactory;
import java.io.File;
@ -35,11 +37,12 @@ import java.security.Provider;
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";
/**
* jceks
* JCEKSJava Cryptography Extension Key Store
*/
public static final String TYPE_JCEKS = "jceks";
/**
@ -100,6 +103,31 @@ public class KeyStoreUtil {
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>

View File

@ -30,6 +30,7 @@ import java.security.Provider;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
/**
* 数字证书{@link Certificate}相关工具类
@ -141,4 +142,35 @@ public class CertUtil {
}
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>
<version>${project.parent.version}</version>
</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) -->
<dependency>
<groupId>jakarta.xml.soap</groupId>

View File

@ -16,7 +16,10 @@
package org.dromara.hutool.http.server;
import org.dromara.hutool.core.net.ssl.SSLContextUtil;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
/**
* 服务器配置
@ -115,6 +118,18 @@ public class ServerConfig {
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上下文
*

View File

@ -125,20 +125,34 @@ public class TomcatEngine extends AbstractServerEngine {
// SSL配置
final SSLContext sslContext = config.getSslContext();
if(null != sslContext){
final SSLHostConfig sslHostConfig = new SSLHostConfig();
final SSLHostConfigCertificate sslHostConfigCertificate =
new SSLHostConfigCertificate(sslHostConfig, SSLHostConfigCertificate.Type.RSA);
sslHostConfigCertificate.setSslContext(new JSSESSLContext(sslContext));
sslHostConfig.addCertificate(sslHostConfigCertificate);
connector.addSslHostConfig(sslHostConfig);
protocol.setSSLEnabled(true);
protocol.setSecure(true);
protocol.addSslHostConfig(createSSLHostConfig(sslContext));
connector.setScheme("https");
connector.setSecure(true);
connector.setPort(config.getPort());
}
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
*

View File

@ -1,12 +1,23 @@
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.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
public class JettyTest {
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");
engine.init(ServerConfig.of());
engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> {
Console.log(request.getPath());
response.write("Hutool Jetty response test");

View File

@ -1,12 +1,23 @@
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.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
public class SunServerTest {
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");
engine.init(ServerConfig.of());
engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> {
Console.log(request.getPath());
response.write("Hutool Sun Server response test");

View File

@ -1,12 +1,23 @@
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.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
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");
engine.init(ServerConfig.of());
engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> {
Console.log(request.getPath());
response.write("Hutool Tomcat response test");

View File

@ -16,13 +16,24 @@
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.net.ssl.SSLContextUtil;
import org.dromara.hutool.crypto.KeyStoreUtil;
import org.dromara.hutool.http.server.ServerConfig;
import javax.net.ssl.SSLContext;
import java.security.KeyStore;
public class UndertowTest {
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");
engine.init(ServerConfig.of());
engine.init(ServerConfig.of().setSslContext(sslContext));
engine.setHandler((request, response) -> {
Console.log(request.getPath());
response.write("Hutool Undertow response test");