在 Tomcat 里搞 HTTPS 认证,实际上跟做个项目没啥关系,主要就是解决“我算不算保险”这个难题。
那会儿写个好办 Demo 时,团队里有人非要把 SSL 证书绑定在启动命令里,结局一部署就崩;后来改成用环境变量加载,配置乱了再也没少过。
这两路走法,本质上都是把证书当成个“配置文件”给 Tomcat 投喂,区别在于如何传,还有最终看起来是不是那么回事。 实际上 Tomcat 的 HTTP 和 HTTPS 认证逻辑挺像的,都是靠个 `SSLEngine` 要么 `HttpEntityWriter` 来负责握手和验签。
这里最头疼的是那个“证书链”难题,也是新手最好办踩坑的地方。大量开发者习惯只放一个 `ca.pem`,指望浏览器自带那个复杂的证书链自动搞定。但仔细想想,浏览器里的信任链可多了去了,要是只给 Tomcat 一个根证书,万一你想部署一个子证书,要么涉及自签名证书的时候,就直接卡死了,连个连接都打不通。
这时候得把整个的信任链连同中间证书一起打包喂进去,不然服务器端压根认不出来客户端的身份。 有时候大家会为了提升效率,把整个信任链压缩成一个 JSON 要么 XML 文件,让它只在启动时跑一遍解析,之后每次请求直接查缓存。
这种方式确实省了点启动工夫,可是你要是想动态换证书,要么证书链里混入了你自己生成的中间证书,这种机制就彻底废了,出于解析器得重新跑一遍整个链,效率感人。 回到核心配置上,要是你用的是标准的 `server.xml` 要么 `host-manager.xml`,想实现双向认证,那得先搞定 `SSLEngine` 的创建逻辑。
一般在 `initServer` 方式里把 SSL 上下文预备好,然后再用 `SSLEngine` 实例去执行握手。
这里有个细节特别关键,就是 `verifyClient` 这个参数,要是设为 `true`,Tomcat 会尝试用客户端的 `ClientCertificate` 去认证;要是设为 `false`,就只靠服务器签的证书验证,这样配置略微好办点,不用管客户端死活。
不过在 Tomcat 8 之后,出于保险寻思,这个参数默认往往就是 `false`,要么更倾向于在客户端那边做独立校验,而不是强制要求整个链路都通过服务器端的单向判断。 说到客户端认证,Tomcat 默认是准 `Basic` 认证的,也就是直接传用户名密码。但这玩意儿保险性忒低了,不保险公司都敢给前端接口加个 Basic 认证,结局黑客随意在浏览器填个字典就能刷爆你的接口。真正想搞点“双因素”要么“强验证”,那就得引入 `CertiK` 要么 `OpenSSL` 这种库,把证书、私钥、密钥库这些文件都打包到 WAR 包要么特别保险的目录里,启动时加载。
这样每次请求来的时候,服务器就拿着客户端传来的公钥,去验证是不是对应的那个私钥能生成这个公钥。 举个例子,假设你的项目配置了 `serviceA.pem` 作为公钥,`serviceA.key` 作为私钥,`serviceA.crt` 作为证书。你在 `host-manager.xml` 要么 `server.xml` 里设置 `useClientCert="true"`,然后告诉 Tomcat 证书生在那个文件夹里。启动的时候,Tomcat 会读取 `serviceA.crt` 和 `serviceA.key`,建立密钥对。接下来请求来时,服务器拿客户端发来的证书,用私钥解密,拿到公钥,再去查配置,看看解密出来的公钥是不是配置里那个 `serviceA` 公钥。对上了就过,没对上就红,连页面上的毛病提示都发不准。 这里有个反直觉的点,大量新手当作只要配置了 `SSLEngine` 和客户端证书路径,就等于搞定了验证。
实际上不然,Tomcat 会尝试用 `ClientCertificate` 来握手,但要是你用的是自签名证书,要么证书链不整个,握手阶段就会报错。
这时候要是你配置了 `SSLCertificateChainValidity` 要么自定义的验证器,Tomcat 就会自己填上默认的链证明,这时候就需求手动把整个信任链告诉 Tomcat,而不只是是根证书。 另外,关于连接保险,大家总揪心 SSL 握手时流量泄露。
实际上 Tomcat 默认是加密的,HTTPS 协议本身就把所有字段包含握ши握、加密套件、协商参数都打包了。
只要你的配置里 `useSSL="true"` 并且设置好了 `sslProtocol` 要么 `sslCipherSuites`,比如只准 `TLSv1.2` 要么 `TLSv1.3`,那么传输过程中的所有敏感数据都是加密的。
不过要注意,要是你启用了 `httpProxy` 要么某些特殊的代理机制,那传输协议可能会退回到 HTTP,这时候就得配合 `SSLProxy` 组件才行,不然数据照样裸奔。 还有种情况,就是混合认证。你既有公钥认证,又有密码认证。
这时候就得搞个 `HttpEntityWriter`,在握手成功后,把客户端传过来的公钥和客户端传过来的密码一起写进 `HttpEntity`,然后传给 `SSLEngine`。服务端拿到这个包,先验签公钥,验签通过了,再解密密码比对。
这种配置略微复杂点,出于得保证握手过程的代码路径和证书校验路径是同步的,不然客户端发公钥的时候,密码还没传,服务端先验签公钥了,密码验证就不成立了,整个流程就断了。 实际上说到底,Tomcat 的 HTTPS 配置核心就两点:把信任链喂进去,把客户端身份收集起来。别总想着找啥新奇的 `SSLEngine` 黑科技,老老实实把 `useClientCert` 设对,把证书链导对,让 Tomcat 自己跑一遍握手逻辑。至于具体的加密强度、协议版本这些,看业务需求定,别为了保险伤了效率,也别为了效率牺牲了保险。毕竟哪家公司不认定 HTTPS 靠谱,不认定配置错了会连服务器都挂?配置错了直接封 IP,比让你改个 SSL 配置还费事。
故此最终就是,把配置写稳,把证书链补全,剩下的交给 Tomcat 去跑。