跳转到内容

HTTPS 配置

Feat 通过 SslPlugin 插件为 HTTP 服务提供 HTTPS 支持。你不需要重写业务代码,只需添加 SSL 插件即可将 HTTP 服务升级为 HTTPS。

使用 PEM 格式证书创建 HTTPS 服务:

import io.github.smartboot.socket.extension.plugins.SslPlugin;
import io.github.smartboot.socket.extension.ssl.factory.PemServerSSLContextFactory;
import tech.smartboot.feat.Feat;
import java.io.InputStream;
public class HttpsDemo {
public static void main(String[] args) throws Exception {
// 加载证书文件
InputStream certPem = HttpsDemo.class.getClassLoader()
.getResourceAsStream("example.org.pem");
InputStream keyPem = HttpsDemo.class.getClassLoader()
.getResourceAsStream("example.org-key.pem");
// 创建 SSL 插件
SslPlugin sslPlugin = new SslPlugin(
new PemServerSSLContextFactory(certPem, keyPem)
);
// 启动 HTTPS 服务
Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> req.getResponse().write("Hello HTTPS"))
.listen();
}
}

本地开发推荐使用 mkcert 生成本地可信证书:

Terminal window
# 安装 mkcert
brew install mkcert # macOS
# 或下载对应系统的二进制文件
# 生成证书
mkcert example.com "*.example.com" localhost 127.0.0.1 ::1

执行后会生成两个文件:

  • example.com+4.pem - 证书文件
  • example.com+4-key.pem - 私钥文件

将这两个文件放入项目的 src/main/resources 目录。

测试环境可以使用 AutoServerSSLContextFactory 自动生成自签名证书:

import io.github.smartboot.socket.extension.ssl.factory.AutoServerSSLContextFactory;
SslPlugin sslPlugin = new SslPlugin(new AutoServerSSLContextFactory());
Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> req.getResponse().write("Auto HTTPS"))
.listen();

支持为不同域名配置不同证书:

import io.github.smartboot.socket.extension.ssl.factory.PemServerSSLContextFactory;
import java.util.HashMap;
import java.util.Map;
// 配置多域名证书
Map<String, PemServerSSLContextFactory> certMap = new HashMap<>();
certMap.put("example.com", new PemServerSSLContextFactory(
certPem1, keyPem1
));
certMap.put("api.example.com", new PemServerSSLContextFactory(
certPem2, keyPem2
));
SslPlugin sslPlugin = new SslPlugin(
(socketAddress, engine) -> {
// 根据域名返回对应证书
return certMap.get(engine.getPeerHost());
}
);

HTTPS 请求的处理方式与 HTTP 完全相同:

Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> {
// 获取请求信息
String method = req.getMethod();
String uri = req.getRequestURI();
// 设置响应
req.getResponse()
.setContentType("application/json")
.write("{\"status\":\"ok\"}");
})
.listen(8443);

通过 SSLEngine 获取 TLS 连接详情:

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> {
SSLEngine engine = req.getSslEngine();
if (engine != null) {
SSLSession session = engine.getSession();
// 获取协议版本
String protocol = session.getProtocol(); // TLSv1.3
// 获取加密套件
String cipher = session.getCipherSuite(); // TLS_AES_256_GCM_SHA384
req.getResponse().write("Protocol: " + protocol);
} else {
req.getResponse().write("Not HTTPS");
}
})
.listen();

如需在业务代码中访问 SSLEngine,需要在插件初始化时进行设置:

import io.github.smartboot.socket.extension.ssl.factory.AutoServerSSLContextFactory;
import tech.smartboot.feat.core.server.HttpRequest;
import javax.net.ssl.SSLEngine;
import java.util.function.Consumer;
SslPlugin sslPlugin = new SslPlugin(
new AutoServerSSLContextFactory(),
(Consumer<SSLEngine>) sslEngine -> {
// 将 SSLEngine 存入 ThreadLocal
HttpRequest.SSL_ENGINE_THREAD_LOCAL.set(sslEngine);
}
);
Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> {
SSLEngine engine = req.getSslEngine();
if (engine != null) {
req.getResponse().write("Cipher: " + engine.getSession().getCipherSuite());
}
})
.listen();

启用双向 TLS 认证,要求客户端提供证书:

import io.github.smartboot.socket.extension.ssl.ClientAuth;
SslPlugin sslPlugin = new SslPlugin(
new PemServerSSLContextFactory(certPem, keyPem),
ClientAuth.REQUIRE // 要求客户端证书
);
Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> {
SSLEngine engine = req.getSslEngine();
if (engine != null) {
// 获取客户端证书信息
javax.security.cert.X509Certificate[] certs =
engine.getSession().getPeerCertificateChain();
req.getResponse().write("Client: " + certs[0].getSubjectDN());
}
})
.listen();

ClientAuth 模式:

模式说明
NONE不验证客户端证书(默认)
REQUEST请求客户端证书,但不强制
REQUIRE强制要求客户端证书
HttpsBasicDemo.java
import io.github.smartboot.socket.extension.plugins.SslPlugin;
import io.github.smartboot.socket.extension.ssl.factory.PemServerSSLContextFactory;
import tech.smartboot.feat.Feat;
import tech.smartboot.feat.core.common.HeaderName;
import tech.smartboot.feat.core.common.HttpStatus;
import java.io.InputStream;
public class HttpsBasicDemo {
public static void main(String[] args) throws Exception {
// 1. 加载证书
InputStream certPem = HttpsBasicDemo.class.getClassLoader()
.getResourceAsStream("server.pem");
InputStream keyPem = HttpsBasicDemo.class.getClassLoader()
.getResourceAsStream("server-key.pem");
if (certPem == null || keyPem == null) {
System.err.println("证书文件未找到");
return;
}
// 2. 创建 SSL 插件
SslPlugin sslPlugin = new SslPlugin(
new PemServerSSLContextFactory(certPem, keyPem)
);
// 3. 启动 HTTPS 服务
Feat.httpServer(opt -> {
opt.addPlugin(sslPlugin);
opt.port(8443);
})
.httpHandler(req -> {
// 获取协议信息
String protocol = req.getProtocol(); // HTTPS/1.1
req.getResponse()
.setHeader(HeaderName.CONTENT_TYPE, "application/json")
.write("{\"message\":\"Secure connection established\"}");
})
.listen();
System.out.println("HTTPS server started on https://localhost:8443");
}
}
HttpsInfoDemo.java
import io.github.smartboot.socket.extension.plugins.SslPlugin;
import io.github.smartboot.socket.extension.ssl.factory.AutoServerSSLContextFactory;
import tech.smartboot.feat.Feat;
import tech.smartboot.feat.core.server.HttpRequest;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import java.util.function.Consumer;
public class HttpsInfoDemo {
public static void main(String[] args) throws Exception {
// 创建带 SSLEngine 回调的 SSL 插件
SslPlugin sslPlugin = new SslPlugin(
new AutoServerSSLContextFactory(),
(Consumer<SSLEngine>) sslEngine -> {
HttpRequest.SSL_ENGINE_THREAD_LOCAL.set(sslEngine);
}
);
Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> {
SSLEngine engine = req.getSslEngine();
StringBuilder info = new StringBuilder();
if (engine != null) {
SSLSession session = engine.getSession();
info.append("Protocol: ").append(session.getProtocol()).append("\n");
info.append("Cipher Suite: ").append(session.getCipherSuite()).append("\n");
info.append("Peer Host: ").append(session.getPeerHost()).append("\n");
}
req.getResponse().write(info.toString());
})
.listen();
}
}

原因:使用的是自签名证书。

解决

  • 本地开发使用 mkcert 生成可信证书
  • 生产环境使用正规 CA 签发的证书
  • 测试环境可手动将证书添加到浏览器信任列表

排查清单

  1. 确认文件名与代码中一致
  2. 确认文件在 src/main/resources 目录下
  3. 确认文件已打包进 jar(检查构建输出)
  4. 使用绝对路径测试:
    InputStream cert = new FileInputStream("/absolute/path/to/cert.pem");

检查项

  1. 确认访问的是 https:// 而非 http://
  2. 确认端口正确(默认 8080,除非显式指定)
  3. 确认防火墙允许该端口
  4. 检查控制台是否有 SSL 相关错误日志

原因:默认情况下 SSLEngine 不会自动设置到请求对象。

解决:创建 SslPlugin 时添加回调:

SslPlugin sslPlugin = new SslPlugin(
sslContextFactory,
(Consumer<SSLEngine>) sslEngine -> {
HttpRequest.SSL_ENGINE_THREAD_LOCAL.set(sslEngine);
}
);

Feat 的 HTTPS 支持 HTTP/2 协议,无需额外配置。客户端使用 ALPN 协商协议版本:

// 服务端代码无需修改,自动支持 HTTP/2
Feat.httpServer(opt -> opt.addPlugin(sslPlugin))
.httpHandler(req -> {
// req.getProtocol() 返回 HTTP/2.0 或 HTTP/1.1
req.getResponse().write("Protocol: " + req.getProtocol());
})
.listen();