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

使用Undertow的Spring Boot Rest服务如何获取入站请求客户端证书的CN

基于Spring Boot + Undertow的客户端证书CN校验实现方案

一、前置配置确认

首先需要确保Undertow已开启客户端证书认证,修改application.yml配置如下:

server:
  port: 8443
  ssl:
    enabled: true
    key-store: classpath:server.keystore
    key-store-password: 你的密钥库密码
    key-alias: 服务端证书别名
    trust-store: classpath:server.truststore
    trust-store-password: 你的信任库密码
    client-auth: need # 强制客户端提交证书,可选为want(非强制)
  undertow:
    max-http-post-size: 10MB

二、两种实现方案

两种方案可根据业务场景选择:

  • 方案1适合部分接口需要校验CN、或不同接口需匹配不同CN规则的场景
  • 方案2适合全局所有接口强制校验CN的场景,校验失败直接在SSL握手阶段拒绝,性能更高

方案1:Servlet过滤器实现(按需校验接口)

  • 第一步:编写自定义过滤器,实现CN校验逻辑
import org.springframework.web.filter.OncePerRequestFilter;
import javax.naming.NamingException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.List;

@Component
public class ClientCertCNFilter extends OncePerRequestFilter {
    // 从配置文件注入合法CN列表,配置项示例:security.client-cert.valid-cns=cn1,cn2
    @Value("${security.client-cert.valid-cns:}")
    private List<String> validCns;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 从请求属性中获取客户端证书链
        X509Certificate[] certChain = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
        if (certChain == null || certChain.length == 0) {
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            response.getWriter().write("Missing client certificate");
            return;
        }
        try {
            String cn = getCertCN(certChain[0]);
            if (cn == null || !validCns.contains(cn)) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                response.getWriter().write("Invalid client certificate CN");
                return;
            }
        } catch (NamingException e) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.getWriter().write("Failed to parse client certificate");
            return;
        }
        filterChain.doFilter(request, response);
    }

    // 从证书中提取CN字段,使用LdapName解析避免手动分割字符串出错
    private String getCertCN(X509Certificate cert) throws NamingException {
        String subjectDn = cert.getSubjectX500Principal().getName();
        LdapName ldapName = new LdapName(subjectDn);
        for (Rdn rdn : ldapName.getRdns()) {
            if (rdn.getType().equalsIgnoreCase("CN")) {
                return rdn.getValue().toString();
            }
        }
        return null;
    }
}
  • 第二步:注册过滤器,指定校验的接口路径
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<ClientCertCNFilter> clientCertCNFilterRegistration(ClientCertCNFilter filter) {
        FilterRegistrationBean<ClientCertCNFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(filter);
        registration.addUrlPatterns("/api/*"); // 仅校验/api路径下的接口,可按需修改
        registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return registration;
    }
}

方案2:Undertow全局信任管理器校验(全接口强制校验)

  • 第一步:自定义X509信任管理器,扩展CN校验逻辑
import javax.naming.NamingException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;

public class CustomX509TrustManager implements X509TrustManager {
    private final X509TrustManager defaultTrustManager;
    private final List<String> validCns;

    public CustomX509TrustManager(X509TrustManager defaultTrustManager, List<String> validCns) {
        this.defaultTrustManager = defaultTrustManager;
        this.validCns = validCns;
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        // 先执行默认的证书信任链校验
        defaultTrustManager.checkClientTrusted(chain, authType);
        // 额外校验CN字段
        try {
            String cn = getCertCN(chain[0]);
            if (cn == null || !validCns.contains(cn)) {
                throw new CertificateException("Client certificate CN is not allowed: " + cn);
            }
        } catch (NamingException e) {
            throw new CertificateException("Failed to parse client certificate CN", e);
        }
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        defaultTrustManager.checkServerTrusted(chain, authType);
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return defaultTrustManager.getAcceptedIssuers();
    }

    private String getCertCN(X509Certificate cert) throws NamingException {
        String subjectDn = cert.getSubjectX500Principal().getName();
        LdapName ldapName = new LdapName(subjectDn);
        for (Rdn rdn : ldapName.getRdns()) {
            if (rdn.getType().equalsIgnoreCase("CN")) {
                return rdn.getValue().toString();
            }
        }
        return null;
    }
}
  • 第二步:自定义Undertow配置,替换默认信任管理器
import org.springframework.boot.web.embedded.undertow.UndertowDeploymentInfoCustomizer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.security.KeyStore;
import java.util.List;

@Configuration
public class UndertowConfig {
    @Value("${security.client-cert.valid-cns:}")
    private List<String> validCns;

    @Bean
    public UndertowDeploymentInfoCustomizer undertowDeploymentInfoCustomizer() throws Exception {
        // 加载系统默认信任管理器
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init((KeyStore) null); // 如需自定义信任库可在此处加载指定KeyStore
        X509TrustManager defaultTm = null;
        for (TrustManager tm : tmf.getTrustManagers()) {
            if (tm instanceof X509TrustManager) {
                defaultTm = (X509TrustManager) tm;
                break;
            }
        }
        CustomX509TrustManager customTm = new CustomX509TrustManager(defaultTm, validCns);
        return deploymentInfo -> deploymentInfo.addSSLContextCustomizer(sslContextBuilder -> {
            sslContextBuilder.setTrustManager(customTm);
        });
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 20:36:02