1. 项目概述为什么要在Tomcat上搞国密双向认证最近在做一个金融行业的项目对接方明确要求通信链路必须使用国密算法并且要做双向认证。这让我不得不把尘封已久的Tomcat配置知识又翻了出来结合GmSSL这个国密“瑞士军刀”折腾了一通。今天就把这次从零开始用GmSSL为Tomcat 9配置基于SM2算法的双向SSL/TLS认证的完整过程、踩过的坑和最终验证方案系统地梳理一遍。如果你也在为合规性要求比如等保、金融行业规范而需要将Web服务从国际通用算法迁移到国密算法栈或者单纯想了解SM2双向认证的实战细节那这篇记录应该能给你提供一个清晰的路线图。简单来说我们要做的是让Tomcat服务器和客户端浏览器或其它客户端在建立HTTPS连接时不仅服务器要向客户端证明自己是可信的单向SSL客户端也要向服务器出示自己的证书来证明身份双向SSL并且整个握手过程中使用的签名算法、密钥交换算法、对称加密算法全部替换为国密套件。这比常见的单向SSL配置要复杂不少涉及到自建根CA、签发双证书签名证书和加密证书、Tomcat连接器配置等多个环节。整个过程的核心工具就是GmSSL它是OpenSSL的一个分支专门集成了国密算法和国密协议。2. 核心概念与准备工作2.1 国密算法与双证书体系解析在开始动手之前必须得先搞清楚几个关键概念不然配置的时候会一头雾水。国密算法是一套中国自主研发的密码算法标准其中SM2是基于椭圆曲线密码ECC的非对称算法用于替代RSASM3是哈希算法替代SHA-256SM4是分组对称加密算法替代AES。我们这次的重点SM2在应用上有一个与国际标准不同的重要特点双证书体系。在国际通用的PKI体系中通常一张RSA证书既用于身份签名签名密钥对也用于密钥交换加密密钥对。但国密标准为了更高的安全性将这两个用途分离了。这意味着一个实体需要两对SM2密钥和两张证书签名证书对应的密钥对用于数字签名和验签证明身份的真实性和数据的不可否认性。在TLS握手过程中服务器用它来签署握手消息如ServerKeyExchange。加密证书对应的密钥对用于密钥交换和数据的加密解密。在TLS握手时客户端会用这张证书的公钥来加密预主密钥Pre-Master Secret并发送给服务器。所以我们的准备工作不仅仅是生成一套密钥和证书而是要为服务器和每一个需要认证的客户端都准备两套东西。理解这一点是后续所有操作的基础。2.2 工具选型与环境准备工欲善其事必先利其器。我们主要需要以下工具GmSSL这是整个流程的基石。建议从GmSSL的官方GitHub仓库下载最新稳定版本的源码进行编译安装。直接用系统包管理器安装的版本可能功能不全或版本过旧。我使用的是在CentOS 7.9系统上编译安装的GmSSL 3.x版本。Tomcat我选用的是Tomcat 9.0.x版本这是一个广泛使用且稳定的版本。Tomcat 8.5及以上版本对国密算法的支持相对更好。确保你的JRE/JDK版本在1.8以上并且最好使用Oracle JDK或OpenJDK并确认其支持相关的安全算法提供商。客户端用于测试。由于主流浏览器Chrome, Firefox默认不支持国密套件我们需要专门的国密浏览器如“密信浏览器”或“零信浏览器”。对于API测试可以使用支持国密算法的客户端库如gmsslPython库或者用GmSSL自带的s_client工具进行调试。操作心得 在Linux上编译安装GmSSL时记得加上--prefix/usr/local/gmssl参数指定安装路径并确保安装后/usr/local/gmssl/bin被加入到系统的PATH环境变量中。可以用gmssl version命令验证安装是否成功并查看支持的国密套件列表gmssl ciphers -v | grep -i SM。3. 构建国密PKI体系创建根CA与签发证书这是最核心也是最容易出错的一步。我们需要建立一个微型的私有CA证书颁发机构并用它来为我们的Tomcat服务器和测试客户端签发签名证书和加密证书。3.1 创建根CA证书首先我们创建一个独立的目录来管理所有密钥和证书避免混乱。mkdir -p /opt/gmssl_ca/{root, server, client} cd /opt/gmssl_ca根CA也需要两对密钥和两张证书。我们先创建签名证书# 生成根CA的SM2签名密钥对 gmssl ecparam -genkey -name sm2p256v1 -out root/ca_sign.key # 生成根CA的签名证书请求CSR gmssl req -new -key root/ca_sign.key -out root/ca_sign.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNMy Root CA Sign # 自签名生成根CA签名证书有效期10年 gmssl x509 -req -days 3650 -in root/ca_sign.csr -signkey root/ca_sign.key -out root/ca_sign.crt接着创建加密证书。注意加密证书的CSR需要用签名私钥来签名。# 生成根CA的SM2加密密钥对 gmssl ecparam -genkey -name sm2p256v1 -out root/ca_enc.key # 生成根CA的加密证书请求 gmssl req -new -key root/ca_enc.key -out root/ca_enc.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNMy Root CA Enc # 用根CA的签名私钥对加密证书请求进行签名生成加密证书 gmssl x509 -req -days 3650 -in root/ca_enc.csr -CA root/ca_sign.crt -CAkey root/ca_sign.key -CAcreateserial -out root/ca_enc.crt现在/opt/gmssl_ca/root目录下应该有ca_sign.key,ca_sign.crt,ca_enc.key,ca_enc.crt四个文件。ca_sign.crt就是我们最终需要导入到客户端和服务器信任库的根证书。3.2 为Tomcat服务器签发双证书服务器证书的生成流程类似但最终需要转换成Tomcat能识别的格式JKS或PKCS12。生成服务器密钥对和CSRcd /opt/gmssl_ca/server # 生成服务器签名密钥对和CSR gmssl ecparam -genkey -name sm2p256v1 -out server_sign.key gmssl req -new -key server_sign.key -out server_sign.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNserver.yourdomain.com # 生成服务器加密密钥对和CSR gmssl ecparam -genkey -name sm2p256v1 -out server_enc.key gmssl req -new -key server_enc.key -out server_enc.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNserver.yourdomain.com注意这里的CNCommon Name非常重要必须填写Tomcat服务器对外提供服务的域名或IP地址。如果使用IP访问这里就填IP。不匹配会导致证书验证失败。用根CA签发服务器证书# 用根CA的签名证书和私钥签发服务器签名证书 gmssl x509 -req -days 365 -in server_sign.csr -CA ../root/ca_sign.crt -CAkey ../root/ca_sign.key -CAcreateserial -out server_sign.crt # 用根CA的签名证书和私钥签发服务器加密证书 gmssl x509 -req -days 365 -in server_enc.csr -CA ../root/ca_sign.crt -CAkey ../root/ca_sign.key -CAcreateserial -out server_enc.crt将服务器证书和密钥转换为PKCS12格式 Tomcat的keystore通常使用JKS或PKCS12格式。由于新版本JDK对JKS的支持在减弱我们选择PKCS12。这里需要一个关键步骤将两张证书和对应的私钥打包到一个PKCS12文件中。GmSSL的pkcs12命令可能无法直接处理双证书我们可以采用一个变通方案先分别打包再考虑合并或者直接使用包含双证书链的单一PKCS12文件如果工具支持。一个更稳妥的方法是直接配置Tomcat使用独立的PEM格式证书和密钥文件需要Tomcat Native库支持。但为了最大兼容性我们尝试创建包含双证书的PKCS12。 实际上更常见的做法是在国密环境中Tomcat通过APR/Native连接器来直接读取PEM文件。我们这里先按此路径准备。 将服务器签名证书、加密证书以及它们对应的私钥合并到一个PEM文件中供Tomcat Native使用cat server_sign.key server_sign.crt server_enc.key server_enc.crt server_combined.pem同时我们也需要将根CA的签名证书ca_sign.crt单独准备好作为信任链。3.3 为测试客户端签发双证书流程与服务器端完全一致只是目录和CN字段不同。cd /opt/gmssl_ca/client # 生成客户端签名密钥对和CSR gmssl ecparam -genkey -name sm2p256v1 -out client_sign.key gmssl req -new -key client_sign.key -out client_sign.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNTest Client # 生成客户端加密密钥对和CSR gmssl ecparam -genkey -name sm2p256v1 -out client_enc.key gmssl req -new -key client_enc.key -out client_enc.csr -subj /CCN/STBeijing/LBeijing/OMyOrg/CNTest Client # 用根CA签发 gmssl x509 -req -days 365 -in client_sign.csr -CA ../root/ca_sign.crt -CAkey ../root/ca_sign.key -CAcreateserial -out client_sign.crt gmssl x509 -req -days 365 -in client_enc.csr -CA ../root/ca_sign.crt -CAkey ../root/ca_sign.key -CAcreateserial -out client_enc.crt # 同样合并为PEM供国密浏览器或客户端使用 cat client_sign.key client_sign.crt client_enc.key client_enc.crt client_combined.pem # 将客户端双证书导出为PKCS12格式供某些客户端导入 # 需要设置一个导出密码比如123456 gmssl pkcs12 -export -out client.p12 -inkey client_sign.key -in client_sign.crt -certfile client_enc.crt -password pass:123456现在客户端的证书文件client.p12密码123456和根证书ca_sign.crt需要分发给测试用户。避坑指南序列号冲突在多次签发证书时GmSSL使用的-CAcreateserial会创建一个序列号文件如ca_sign.srl。如果这个文件已经存在它不会自动递增可能导致序列号重复。稳妥的做法是在每次签发后手动修改这个.srl文件里的值或者使用-set_serial参数指定一个唯一的序列号。证书链缺失在将证书打包或配置时务必确保证书链完整。服务器的PKCS12文件或PEM文件中最好也包含根CA证书ca_sign.crt虽然Tomcat不一定需要但有些客户端验证时需要中间链。密钥格式确保生成的私钥是PKCS#8格式GmSSL默认生成的就是而不是传统的PKCS#1格式以免兼容性问题。4. 配置Tomcat启用国密双向认证有了证书接下来就是配置Tomcat。这里我们选择使用Tomcat的APR/Native连接器因为它能提供更好的性能并且原生支持通过OpenSSLGmSSL库来直接处理PEM格式的证书和密钥完美适配我们的国密双证书。4.1 安装Tomcat Native库Tomcat Native库是Tomcat与本地APR库包括OpenSSL交互的桥梁。确保系统已安装APR开发库和OpenSSL开发库。在CentOS/RHEL上yum install apr-devel openssl-devel在Tomcat的bin目录下通常存在一个tomcat-native.tar.gz源码包。解压并编译cd /usr/local/tomcat/bin tar -xzf tomcat-native.tar.gz cd tomcat-native-*/native ./configure --with-apr/usr/bin/apr-1-config --with-ssl/usr/local/gmssl make make install关键点在于--with-ssl参数必须指向你编译安装GmSSL的目录/usr/local/gmssl这样编译出来的Native库才会链接GmSSL从而支持国密算法。配置环境变量让Tomcat启动时能找到这个库echo export LD_LIBRARY_PATH/usr/local/apr/lib:$LD_LIBRARY_PATH /etc/profile source /etc/profile4.2 配置server.xml连接器接下来修改Tomcat的conf/server.xml文件。找到默认的HTTP/1.1连接器通常是8080端口我们不是修改它而是在其下方添加一个新的、支持HTTPS和APR的连接器。!-- 在Service nameCatalina标签内找到或添加如下Connector -- Connector port8443 protocolorg.apache.coyote.http11.Http11AprProtocol maxThreads150 SSLEnabledtrue schemehttps securetrue SSLHostConfig certificateVerificationrequired protocolsTLSv1.2 ciphersECDHE-SM2-SM4-CBC-SM3,ECDHE-SM2-SM4-GCM-SM3 Certificate certificateFile/opt/gmssl_ca/server/server_combined.pem certificateChainFile/opt/gmssl_ca/root/ca_sign.crt typeRSA / /SSLHostConfig /Connector关键参数解析protocolorg.apache.coyote.http11.Http11AprProtocol指定使用APR连接器这是启用国密支持的关键。port8443HTTPS服务端口。certificateVerificationrequired这就是开启双向认证的开关。required表示客户端必须提供有效的证书否则连接将被拒绝。protocolsTLSv1.2指定TLS协议版本。国密套件通常在TLSv1.2及以上版本中定义。ciphersECDHE-SM2-SM4-CBC-SM3,ECDHE-SM2-SM4-GCM-SM3指定密码套件。这里列出了两个国密套件前者使用CBC模式后者使用GCM模式性能更好。ECDHE-SM2表示使用SM2密钥进行椭圆曲线迪菲-赫尔曼密钥交换。Certificate标签certificateFile指向我们合并的服务器PEM文件包含服务器的签名和加密私钥与证书。certificateChainFile指向根CA的签名证书。在双向认证中服务器需要用这个根证书来验证客户端证书是否由其签发。typeRSA这里是个“障眼法”。由于Tomcat的配置 schema 可能没有明确定义SM2类型填RSA或EC有时也能工作因为底层实际由GmSSL库处理。如果配置不生效可以尝试typeEC或查阅特定版本Tomcat的文档。4.3 配置客户端证书验证与角色映射可选但推荐仅仅要求客户端提供证书还不够我们通常还需要在Web应用中根据客户端证书中的信息如CN组织单位OU等来授予用户不同的角色权限。这需要在Web应用的WEB-INF/web.xml中配置安全约束。首先在conf/server.xml的同一个Connector标签内添加一个SSLHostConfig的子项用于指定如何从客户端证书中提取用户名SSLHostConfig ... Certificate ... / !-- 添加此属性从客户端证书的CN字段提取用户名 -- CertificateVerification certificateVerificationrequired / /SSLHostConfig更精细的控制在应用层面。在你的Web应用比如ROOT或自定义应用的WEB-INF/web.xml中添加如下配置security-constraint web-resource-collection web-resource-nameSecure Area/web-resource-name url-pattern/*/url-pattern !-- 保护所有资源 -- /web-resource-collection auth-constraint role-nametrusted_client/role-name !-- 只允许拥有此角色的用户访问 -- /auth-constraint user-data-constraint transport-guaranteeCONFIDENTIAL/transport-guarantee !-- 要求HTTPS -- /user-data-constraint /security-constraint login-config auth-methodCLIENT-CERT/auth-method !-- 认证方式为客户端证书 -- /login-config security-role role-nametrusted_client/role-name /security-role然后在conf/tomcat-users.xml中将客户端证书的标识如CNTest Client映射到trusted_client角色。但更常见的做法是使用org.apache.catalina.realm.JAASRealm或DataSourceRealm进行动态映射这超出了本文基础配置的范围。简单测试时可以暂时注释掉auth-constraint先确保证书认证本身能通。5. 测试与验证从浏览器到命令行配置完成后重启Tomcat服务。查看logs/catalina.out日志如果看到类似ProtocolHandler [https-jsse-apr-8443]或APR support enabled的日志并且没有SSL相关的错误说明APR连接器启动成功。5.1 使用GmSSL命令行工具测试在服务器本机上我们可以先用GmSSL自带的s_client工具进行最底层的测试这能排除浏览器等上层应用的干扰。# 测试单向SSL服务器证书验证 gmssl s_client -connect localhost:8443 -CAfile /opt/gmssl_ca/root/ca_sign.crt # 测试双向SSL指定客户端证书和密钥 gmssl s_client -connect localhost:8443 \ -CAfile /opt/gmssl_ca/root/ca_sign.crt \ -cert /opt/gmssl_ca/client/client_sign.crt \ -key /opt/gmssl_ca/client/client_sign.key \ -cert_chain /opt/gmssl_ca/client/client_enc.crt # 如果需要指定加密证书链如果连接成功会输出一长串握手信息包括“SSL handshake has read ... bytes and written ... bytes”、“Verify return code: 0 (ok)”等。特别留意密码套件部分应该显示为Cipher is ECDHE-SM2-SM4-GCM-SM3或你配置的国密套件。如果握手失败会显示SSL_connect:error并根据错误码排查。5.2 使用国密浏览器测试安装根证书将根CA证书ca_sign.crt导入到国密浏览器的“受信任的根证书颁发机构”存储中。具体步骤因浏览器而异通常在设置-安全或隐私-证书管理里。导入客户端证书将客户端证书client.p12或client_combined.pem如果浏览器支持导入PEM导入到浏览器的“个人”证书存储中并记住导入时设置的密码我们生成时用的是123456。访问在浏览器地址栏输入https://your-server-ip:8443。浏览器会弹出选择客户端证书的对话框选择我们刚才导入的“Test Client”证书。验证如果一切配置正确页面应能正常打开可能是Tomcat默认页或你的应用页面。按F12打开开发者工具在“安全”(Security)标签页中应该能看到连接使用的协议是TLS 1.2或1.3并且密码套件是国密算法。5.3 常见问题与排查技巧实录在实际操作中几乎不可能一次成功。下面是我遇到的一些典型问题及解决方法问题1Tomcat启动失败日志报错java.lang.UnsatisfiedLinkError或APR not found。原因Tomcat Native库未正确安装或加载。排查检查LD_LIBRARY_PATH环境变量是否包含Native库路径如/usr/local/apr/lib。执行ldd /usr/local/apr/lib/libtcnative-1.so查看是否成功链接到libssl.so应指向GmSSL的库如/usr/local/gmssl/lib/libssl.so.3。确认server.xml中连接器的protocol属性是否正确设置为Http11AprProtocol。问题2HTTPS连接失败GmSSLs_client提示ssl handshake failure或no ciphers available。原因密码套件不匹配或证书问题。排查检查密码套件在server.xml中确保ciphers参数包含正确的国密套件名称。可以用gmssl ciphers -v ALL查看GmSSL支持的所有套件确认你写的套件名存在。检查证书链使用gmssl verify -CAfile ca_sign.crt server_sign.crt验证服务器证书是否由根CA正确签发。同样验证客户端证书。检查证书用途用gmssl x509 -in server_sign.crt -text -noout查看证书详情确认X509v3 Key Usage和X509v3 Extended Key Usage字段包含Digital Signature和Key Agreement等必要用途。虽然GmSSL签发的证书通常已设置正确但值得检查。问题3双向认证时客户端证书已提供但服务器返回403 Access Denied或无法获取用户身份。原因Tomcat未能从客户端证书中正确提取用户名或应用的安全约束配置有误。排查暂时在web.xml中注释掉auth-constraint部分看是否能正常访问。如果能问题在角色映射。在server.xml的Connector中添加clientAuthtrue属性对于APR连接器已通过certificateVerificationrequired设置并确保SSLHostConfig配置正确。查看Tomcat日志localhost_access_log.*看请求是否带有javax.servlet.request.X509Certificate属性。问题4国密浏览器访问时不弹出证书选择框直接报错。原因浏览器未正确识别国密套件或客户端证书格式/存储位置不对。排查确认浏览器确实是支持国密的版本如密信浏览器。确认根证书已正确导入到“受信任的根证书颁发机构”而不是“中级证书颁发机构”。确认客户端证书PKCS12格式已导入到“个人”证书存储并且导入时输入的密码正确。尝试用GmSSLs_client先测试确保服务端配置本身无误。一个实用的调试技巧 在Tomcat的conf/logging.properties中增加SSL相关的日志级别可以获取更详细的握手过程信息org.apache.tomcat.util.net.level FINE javax.net.ssl.level FINE重启Tomcat后观察catalina.out日志能看到更详细的密码套件协商、证书验证过程。6. 性能考量与生产环境建议在测试环境跑通只是第一步要上生产还得考虑更多。证书管理自建根CA只适用于内部测试或封闭环境。生产环境应使用由合规的第三方国密CA机构如CFCA等签发的服务器和客户端证书。证书的申请、签发、续期、吊销需要有一套完整的管理流程。性能SM2算法基于椭圆曲线其计算效率通常比同等安全强度的RSA更高。但国密TLS握手过程由于涉及双证书可能会比国际单证书略慢一点。启用会话复用TLS Session Resumption能显著提升频繁连接场景下的性能。在server.xml的SSLHostConfig中可以配置sessionTimeout等参数。高可用与负载均衡如果Tomcat集群部署在负载均衡器如Nginx, F5后面双向认证的终止点Termination可以放在负载均衡器上也可以穿透Pass-through到后端Tomcat。前者减轻后端压力但需要负载均衡器本身支持国密算法和双证书后者配置简单但证书和私钥需分发到每一台后端服务器管理更复杂。需要根据实际架构和安全要求选择。密码套件优先级在ciphers配置中排在前面的套件会被优先协商。应将更安全、性能更好的套件如ECDHE-SM2-SM4-GCM-SM3放在前面。禁用不安全的协议如SSLv3, TLSv1.0, TLSv1.1。Tomcat Native维护Tomcat Native需要单独编译维护且与Tomcat版本、系统环境相关。升级Tomcat或系统OpenSSL/GmSSL时可能需要重新编译Native库这一点在制定升级计划时要考虑进去。整个配置过程最磨人的地方往往在于证书链的完整性和格式转换以及Tomcat连接器配置参数的精确定义。一旦理解了国密双证书的“双路径”逻辑剩下的就是耐心调试和验证。建议在每一步操作后都用gmssl命令验证一下生成的密钥、证书、签名是否有效能提前发现很多问题。最后别忘了在一切就绪后用专业的国密检测工具或服务如果有条件对部署的服务进行一次全面的国密合规性扫描确保没有配置遗漏或安全弱点。