1. 背景
目前公司对外开放了一个云服务平台,提供一些功能供商户接入使用。整个项目的架构是基于Spring + MyBatis的。另外,商户端的服务接口是基于SOAP WebService的,这部分使用CXF实现。
安全方面采用了Spring Security,可以对商户提供证书认证或密码认证。但是出于安全考虑,目前只开放了证书认证。为了使用证书认证商户,我们创建了一个自签名的CA,用来生成商户使用的客户端证书。在验证上,使用Nginx验证客户端证书是否是指定CA产生的。另外,为了防止被作废的证书(例如给商户颁发了新证书后,原证书应该作废,但是原证书也是由指定CA产生的)再次使用,在代码层面对证书进行了进一步的验证(这一点是通过Nginx将客户端证书作为Header传递到Java后台实现的,有时间以后再讲)。
1.1. 部署架构
云服务平台部署在aliyun上,大致上结构是这样的(只涉及到了网络访问层面的东西):
商户 -https-> aliyun负载均衡 -tcp转发-> Nginx -http-> Jetty --> ClientCertificateFromProxyFilter
商户访问云服务的时候,需要使用我们提供的客户端证书来建立https链接。aliyun的LB只负责TCP转发,不对协议进行分析。因此从aliyun到Nginx之间实际的数据流是https数据流。Nginx接受到请求后,验证客户端证书是否正确,并将客户端证书设置为HTTP请求中的Header变量,然后请求后台的Jetty服务器。
我们代码中实现了一个Filter(ClientCertificateFromProxyFilter),它的唯一作用是检查过来的HTTP请求中有没有变量SSL_CLIENT_CERT
,如果有则把它转换成一个Certificate对象,添加到HTTP请求中,从而将一个HTTP请求模拟成一个HTTPS请求,这样Spring Security就能够进行证书认证了。
3. 问题
在上线之前,按照以往的经验,我们测试了通过浏览器访问受保护的资源来测试HTTPS是否工作正常。因为提前在浏览器中导入了客户端证书,因此浏览器上能够弹出对话框选择客户端证书,选择之后就能够访问指定的资源了。
我们推荐商户使用CXF作为接入方式,一般的代码如下:
<jaxws:client id="uidService"
serviceClass="com.xwf.cloudauth...."
address="https://.../api/UidApiService">
</jaxws:client>
<http-conf:conduit name="*.http-conduit">
<http-conf:tlsClientParameters disableCNCheck=