Skip to content

HTTPS 配置

This content is not available in your language yet.

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();