You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中使用自签名证书实现TLS客户端认证

Java 实现 Gemini 服务器支持自签名客户端证书认证

核心思路

  • 自定义X509TrustManager,跳过默认的CA链验证逻辑,允许所有客户端证书通过
  • 在SSL握手完成后,从会话中提取客户端证书,计算其哈希值(如SHA-256,对应CertFP的常用算法)
  • 将哈希值作为伪匿名用户的唯一标识进行后续认证逻辑

具体实现步骤

1. 自定义信任管理器

编写一个信任所有证书的X509TrustManager,仅跳过验证逻辑,但保留证书获取能力:

import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;

public class AllTrustingTrustManager implements X509TrustManager {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) {
        // 跳过客户端证书的CA验证,直接信任所有证书
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) {
        // 服务器端自身的证书验证逻辑,若不需要可留空(建议保留默认逻辑或自定义)
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
}

2. 初始化SSLContext

配置服务器的SSL上下文,使用自定义信任管理器,同时加载服务器自身的证书(Gemini要求服务器必须有证书):

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import java.io.FileInputStream;
import java.security.KeyStore;

public SSLContext createGeminiSSLContext() throws Exception {
    // 加载服务器自身的密钥库(比如JKS格式)
    KeyStore keyStore = KeyStore.getInstance("JKS");
    try (FileInputStream fis = new FileInputStream("server-keystore.jks")) {
        keyStore.load(fis, "keystore-password".toCharArray());
    }

    // 初始化密钥管理器
    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keyStore, "key-password".toCharArray());

    // 使用自定义信任管理器
    TrustManager[] trustManagers = new TrustManager[]{new AllTrustingTrustManager()};

    // 初始化SSLContext,使用TLS 1.2+(Gemini推荐TLS 1.3)
    SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
    sslContext.init(kmf.getKeyManagers(), trustManagers, null);

    return sslContext;
}

3. 启动Gemini服务器并处理客户端连接

创建SSLServerSocket,要求客户端提供证书,然后提取证书计算哈希:

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSession;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.cert.X509Certificate;

public class GeminiServer {
    public static void main(String[] args) throws Exception {
        SSLContext sslContext = createGeminiSSLContext();
        SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
        SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(1965);

        // 要求客户端提供证书
        serverSocket.setNeedClientAuth(true);

        while (true) {
            Socket clientSocket = serverSocket.accept();
            new Thread(() -> handleClient(clientSocket)).start();
        }
    }

    private static void handleClient(Socket clientSocket) {
        try {
            // 获取客户端会话中的证书链
            SSLSession session = ((javax.net.ssl.SSLSocket) clientSocket).getSession();
            X509Certificate[] clientCerts = (X509Certificate[]) session.getPeerCertificates();
            String certHash = null;

            if (clientCerts.length > 0) {
                // 取第一个证书(客户端的身份证书)计算SHA-256哈希
                X509Certificate clientCert = clientCerts[0];
                byte[] certBytes = clientCert.getEncoded();
                MessageDigest md = MessageDigest.getInstance("SHA-256");
                byte[] hashBytes = md.digest(certBytes);

                // 转成小写十六进制字符串(和CertFP格式一致)
                StringBuilder hashHex = new StringBuilder();
                for (byte b : hashBytes) {
                    hashHex.append(String.format("%02x", b));
                }
                certHash = hashHex.toString();

                // 这里可以用certHash作为用户标识,进行伪匿名认证逻辑
                System.out.println("客户端证书哈希: " + certHash);
            }

            // 处理Gemini请求(示例:返回成功响应)
            OutputStreamWriter out = new OutputStreamWriter(clientSocket.getOutputStream());
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            String requestLine = in.readLine();
            out.write("20 text/plain\r\n");
            out.write("欢迎使用Gemini服务器,你的证书哈希:" + (certHash != null ? certHash : "未提供证书") + "\r\n");
            out.flush();

            in.close();
            out.close();
            clientSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 嵌入之前的createGeminiSSLContext方法
    private static SSLContext createGeminiSSLContext() throws Exception {
        // 实现代码同上
    }
}

关键注意事项

  • setNeedClientAuth(true)必须设置,强制客户端提供证书
  • 哈希算法建议使用SHA-256,和IRC CertFP、Mumble的认证格式保持一致
  • 虽然跳过了CA验证,但仍可在checkClientTrusted中添加自定义校验逻辑(比如检查证书有效期)
  • 服务器自身的证书必须合法,否则客户端可能拒绝连接

内容的提问来源于stack exchange,提问作者Alex Lehmann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 07:53:13