HTTPS通信的具体原理,这里不做介绍。作为一个C/S的通信协议,HTTPS是分为Client和Server的,这里会介绍当Tomcat WEB Server分别作为HTTPS的客户端和服务器端时候的配置和实现。
WEB容器作为HTTPS的服务器端
Tomcat提供HTTPS Server的功能和浏览器等其他HTTPS客户端进行交互是很常见的使用方式。Tomcat配置实现了HTTPS Server之后,浏览器就可以通过https协议访问Tomcat提供的WEB服务了。
具体配置步骤如下:
生成证书
可以用java自动的工具生成证书,如果已经有可信的第三方签发的证书或者其他工具生成的服务器证书,此步骤可以忽略,直接进行后面的步骤。
服务器证书用于后续发生SSL协商时,发给SSL Client用来验证服务器的。在HTTPS应用场景中,默认SSL Client一般是浏览器,因为浏览器出厂的时候会内置一些可信的第三方CA作为验证的CA链。所以此处如果使用了这些第三方CA签发的证书作为服务器的证书,则浏览器不用做任何设置就可以对服务器端进行有效的验证。否则,因为验证证书需要一直到Root CA建立信任关系链,这里对于自己生成的证书有两种处理的方式:
- 在本地建立CA,签发服务器证书给Tomcat使用,将本地CA证书链导入浏览器作为可信的CA,后续用来在浏览器端验证Tomcat的服务器证书。
- Tomcat的浏览器证书使用自签名证书,这样客户端就不用再做任何配置了。
上述两种方式在WEB开发的测试过程中使用都是没有问题,一般用第二种方法,更简洁一些。但是在实际的生产环境,是行不通的。
- 方法一:因为WEB服务面向的访问群体不确定,我们没有办法将自己生成的CA证书链导入到所有的浏览器中。
- 方法二:自签名证书可以协商安全的SSL通道,保证HTTP通信的通道安全,但是因为是自签名的,客户端无法确认服务器端的身份,无法阻止基于中间人的钓鱼攻击。所以最好的方式还是使用可信的第三方签发的证书,当然这种服务是收费的。
我们下面只说明一下测试环境中证书的使用,这里使用自签名证书做示例:
生成证书
这里使用了网上的图片做说明,大致步骤是相同的,注意,这里的common-name,也就是名字和姓氏那一栏需要和WEB域名一致。在实际应用过程中,浏览器判断WEB Server的身份是否可行的通用做法是比较证书字段中的common-name和WEB的域名是否相同,如果不相同会提示WEB Server不可信。
导出证书
上一步中只是在keystore中生成了一个证书,这个keystore中可以有很多证书,这里我们把需要的证书文件导出,导出的时候请指定别名。记下导出的证书的路径,后面需要用到。
至此,证书的操作就已经完成了。
配置Tomcat服务器
Tomcat服务器默认提供了HTTP服务,如果需要用HTTPS服务,需要打开相应的端口,和做一些必要的配置。
如上图所示,protocol中使用的是coyote方式,如果是其他的方式,如APR方式,可能配置方法会不同,具体的如果有需求,可以到网上搜索相关资料。
注意:这里keystoreFile选择当时生成的证书库,keystorePass填写自己的密码即可。
使用HTTPS服务登录
将配置好的tomcat服务器重启之后,会自动加载证书库中的证书,根据请求的域名发
送相关的证书。如图:
这里因为用的是自签名证书,所以提示是无效的证书认证,我们可以选择继续访问,可以正常进入首页。
WEB容器作为HTTPS的客户端
Tomcat因为是WEB服务的提供者,所以作为HTTPS的服务器端是很好理解的。但是在实际应用中WEB服务器也是很有可能作为HTTPS的客户端的。解决跨域访问就是一个这样的应用场景。考虑下面的情况:
在www.A.com的网站中,需要获取www.B.com中的某一个资源,正常的想法是,通过B提供的API直接在A的页面中发送HTTP Get/Post请求到B的服务器中获取,这样是否可以呢?实际是不行的,因为浏览器有同源策略,是会默认禁止在一个域中向另外一个域中直接发起请求的,如果这样做了,会出现一个access-control-allow-origin的错误,这里不仔细介绍,如果有兴趣的可以搜索一下相关的内容。
我们可以看到,在浏览器中,直接在A的页面中获取B的资源是不可行的,但是实际上我们又确实有这样的需求,怎么办呢?可以通过A的后台做代理去B获取资源,然后再将获取的资源返回给A。这时候WEB 服务器(tomcat)就有可能作为HTTPS的客户端了。
准备证书
在HTTPS的使用场景中,一般只会要求对服务器进行认证,目前不会也不太可能对客户端进行认证。所以,作为HTTPS Client的时候,本身是不需要有证明自己身份的证书的。但是因为要验证服务器,所以需要有可信任的证书链。寻找可信任的证书链的具体的原则如下:
客户端的TrustStore文件中保存着被客户端所信任的服务器的证书信息。客户端在进行SSL连接时,JSSE将根据这个文件中的证书决定是否信任服务器端的证书。在SunJSSE中,有一个信任管理器类负责决定是否信任远端的证书,这个类有如下的处理规则:
- 若系统属性javax.net.sll.trustStore指定了TrustStore文件,那么信任管理器就去jre安装路径下的lib/security/目录中寻找并使用这个文件来检查证书。
- 若该系统属性没有指定TrustStore文件,它就会去jre安装路径下寻找默认的TrustStore文件,这个文件的相对路径为:lib/security/jssecacerts。
- 若jssecacerts不存在,但是cacerts存在(它随J2SDK一起发行,含有数量有限的可信任的基本证书),那么这个默认的TrustStore文件就是lib/security/cacerts。
所以我们在实际使用中,要看看具体证书放在哪里,java1.8中,证书是放在lib/security/cacerts中的。这里就包含了默认的一些第三方证书机构的可信的CA证书链。只要我们要连接的HTTPS服务器端的证书是这些第三方证书机构颁发的,则不需要做任何处理。如果服务器端没有使用自己的CA签发的证书或者自签名证书怎么办呢?有两个解决办法:
- 按照以上信任管理器的规则,将服务端的公钥导入到jssecacerts,或者是在系统属性中设置要加载的trustStore文件的路径;证书导入可以用如下命令:keytool -import -file src_cer_file –keystore dest_cer_store;至于证书可以通过浏览器导出获得;
- 实现自己的证书信任管理器类,比如MyX509TrustManager,该类必须实现X509TrustManager接口中的三个method;然后在HttpsURLConnection中加载自定义的类。
第一种方法稍嫌繁琐,第二种方法可能会更方便一些,但是有可能导致安全问题。因为我们这里主要是和阿里的HTTPS服务器连接,阿里用的是可信第三方CA颁发的证书,所以不存在上述说的证书无法验证的问题。
至此,证书已经准备好了,可以进行编码验证了。
连接HTTPS服务器端
这部分工作比较简单,在Java中要访问Https链接时,会用到一个关键类HttpsURLConnection, 参见如下实现代码:
创建一个URL的实例,然后打开链接,就可以进行一次Request操作了,通过InputStreamReader类可以获取返回的信息。实际测试过,可以从阿里的认证服务器获取回应信息。具体的请求的参数和返回数据的处理可以在实际的操作过程中进行完善。