org.eclipse.jetty.http.BadMessageException:400:无效的 SNI

问题描述 投票:0回答:4

我正在尝试解决通过 HTTPS 运行的 Jetty servlet 的问题。

浏览器中显示此错误页面:

我做了什么:

  1. 我创建了我的密钥库和信任库,如下所述:如何生成密钥库和信任库

  2. 这两个文件都放在我的项目目录中,并编写了代码来加载这些文件。

package sk.cood.metahost.server;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.ssl.SslContextFactory;

import java.io.*;

@WebServlet(displayName = "MetaHostServlet", urlPatterns = { "/*" })
public class MetaHostServlet extends HttpServlet {
    private static File keyStoreFile;
    private static File trustStoreFile;

    public static void main(String[] args) throws Exception {
        loadKeyStores();

        Server server = new Server(8080);
        ServerConnector connector = createSSLConnector(server, "password", "password", false);
        server.addConnector(connector);

        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        context.addServlet(new ServletHolder(new MetaHostServlet()),"/*");
        context.setContextPath("/");
        server.setHandler(context);

        server.start();
        server.join();
    }

    private static void loadKeyStores() {
        keyStoreFile = new File("keystore.jks");
        trustStoreFile = new File("truststore.jks");
        if (!keyStoreFile.exists()) {
            throw new RuntimeException("Key store file does not exist on path '"+keyStoreFile.getAbsolutePath()+"'");
        }
        if (!trustStoreFile.exists()) {
            throw new RuntimeException("Trust store file does not exist on path '"+trustStoreFile.getAbsolutePath()+"'");
        }
    }

    private static ServerConnector createSSLConnector(Server server, String keyStorePassword, String trustStorePassword, boolean isClientAuthNeeded) {
        SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
        sslContextFactory.setKeyStorePath(keyStoreFile.getAbsolutePath());
        sslContextFactory.setKeyStorePassword(keyStorePassword);
        sslContextFactory.setTrustStorePath(trustStoreFile.getAbsolutePath());
        sslContextFactory.setTrustStorePassword(trustStorePassword);
        sslContextFactory.setNeedClientAuth(isClientAuthNeeded);

        HttpConfiguration https_config = new HttpConfiguration();
        https_config.setSendServerVersion(false);
        https_config.setRequestHeaderSize(512 * 1024);
        https_config.setResponseHeaderSize(512 * 1024);

        SecureRequestCustomizer src = new SecureRequestCustomizer();
        https_config.addCustomizer(src);

        return new ServerConnector(server, sslContextFactory, new HttpConnectionFactory(https_config));
    }

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
        res.setContentType("text/html");
        res.setStatus(HttpServletResponse.SC_OK);
        res.getWriter().println("<h1>Hello World!</h1>");
        res.getWriter().println("session=" + req.getSession(true).getId());
    }
}
  1. 我用 jetty 启动了我的 servlet 并尝试连接到 localhost:8080 并出现了提到的错误。

然后我尝试通过其他指南创建其他密钥库和信任库,例如,我向新密钥库添加了 DNS 备用名称,如下所述:https://serverfault.com/questions/488003/keytool-subjectalternativename 但什么也没有帮助了我。

我不知道我的情况出了什么问题,也许对码头和证书更有经验的人会有所帮助。

非常感谢!

java servlets https jetty keystore
4个回答
8
投票

当我从本地运行应用程序时,我也遇到了同样的问题。由于 SNI 不允许使用 localhost,因此我在代码中暂时禁用了 SNI 检查。以下是我为在本地运行应用程序而修改的代码:

SecureRequestCustomizer src = new SecureRequestCustomizer();
src.setSniHostCheck(false);
https_config.addCustomizer(src);

注意:对于 PROD 版本,SniHostCheck 仍然设置为 true。而且我不建议任何人将其设置为 false。


6
投票

此消息意味着 HTTP 客户端的 TLS 层提供的 SNI 与密钥库中的 SNI 主机之一不匹配。

知道SNI本身有限制:

  • localhost
    不允许
  • 不允许使用 IP 地址文字(例如:
    192.168.1.215
    fe80::3831:400c:dc07:7c40
  • 所有 SNI 主机名的名称中必须至少包含 1 个
    .
    (因此不要使用速记主机/域名,使用完全限定的规范主机/域名)

这些限制在规范中,并且通常是大多数问题(例如您遇到的问题)的原因。

根据您的 Java 版本,这些 SNI 限制在 Java 网络堆栈中检查的速度比其他版本的 Java 更快。 (Java 11 的主机名限制中的

.
限制较少,而 Java 17+ 则强制执行)


0
投票

对于自签名密钥库,需要将主机名(例如 localhost)放入名称中是很棘手的。

https://community.pivotal.io/s/article/Generate-a-self-signed-SSL-certificate-using-the-Java-keytool-command?language=en_US

“当提示输入名字和姓氏时,请输入服务器的域名。例如,myserver 或 myserver.mycompany.com”


0
投票

改编自 @nitin-singhal 的答案,添加前后上下文以更好地理解。

之前:

HttpConnectionFactory(new HttpConfiguration());

之后:

// Disable SNI checks for easier testing (replicates Jetty 9.x behavior)
HttpConfiguration httpsConfig = new HttpConfiguration();
SecureRequestCustomizer customizer = new SecureRequestCustomizer();
customizer.setSniHostCheck(false);
httpsConfig.addCustomizer(customizer);

HttpConnectionFactory httpConnection = new HttpConnectionFactory(httpsConfig);
© www.soinside.com 2019 - 2024. All rights reserved.