异常:GlassFish 5 不能与某些带有SSL证书的服务器进行通信 (SSL/TLS握手问题)

工作 专栏收录该内容
2 篇文章 0 订阅

1.搭建环境 

    1) Eclipse Jee Pthoton

    2) GlassFish 5.0

    3) JDK8u201

2.场景

在GlassFish服务器上发布一个网页,用户通过键入地名来获取相关的邮编。 后台接收地名并调用政府服务器的一个api来返回邮编。

3.SSL Client 代码

新建一个SSL Client, 调用api并取回JSON字符串:

String city = xxx;
String StrUri = "https://geo.api.gouv.fr/communes?nom="+city;

ClientConfig clientConfig = new ClientConfig();
Client client = ClientBuilder.newClient(clientConfig);

URI serviceUri = UriBuilder.fromUri(StrUri).build();
WebTarget webTarget = client.target(serviceUri);

String jsonString = webTarget.request().accept(MediaType.APPLICATION_JSON).get(String.class);

4.SSL handshake 

api(https://geo.api.gouv.fr/communes?xxxxx)的前缀为https,所以这个政府的服务器带有SSL/TLS证书, 也就是说它使用传输层安全性协议进行SSL/TLS通信。

在SSL client 与SSL server通信之前,它们需要建立安全验证,这就叫SSL/TLS基本握手(SSL/TLS handshake)。

 

其中Client要验证Server发来的证书的有效性,是怎么做的呢?

首先,Client是在GlassFish上建立的,它会查看在GlassFish里的存放证书的文件,根据其官方文档(安全手册 p26) 的说明,GlassFish有两个存放证书的文件:

  • cacerts.jks 包含着被GlassFish信任的CA (root 或 intermediate) 的证书,包括它们的公钥
  • keystore.jks 包含着GlassFish自己的一些证书,包括私钥

其次,根据政府服务器发来的证书,Client 会检测此证书的CA信息,然后与cacerts.jks的对应CA信息对比,以检验刚发来的证书是否有效。

 

接下来我们打开网页,输入地名,然后调用api返回邮编,结果出现了些问题。

5.问题一

 javax.ws.rs.ProcessingException: sun.security.ssl.SSLSessionImpl.<init> 

javax.ws.rs.ProcessingException: sun.security.ssl.SSLSessionImpl.<init>(Lsun/security/ssl/ProtocolVersion;Lsun/security/ssl/CipherSuite;Ljava/util/Collection;Lsun/security/ssl/SessionId;Ljava/lang/String;I)V
	at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:287)
	at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$1(JerseyInvocation.java:767)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:316)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:298)
	at org.glassfish.jersey.internal.Errors.process(Errors.java:229)
	at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:414)
	at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:765)
	at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:428)
	at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:324)
	at service_sncf.ClientSncf.getCog(ClientSncf.java:59)
	at borne.BorneCalculDistance.doPost(BorneCalculDistance.java:58)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:706)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:791)
	at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1580)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:258)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:160)
	at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:652)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:591)
	at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:99)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:155)
	at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:371)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:238)
	at com.sun.enterprise.v3.services.impl.ContainerMapper$HttpHandlerCallable.call(ContainerMapper.java:463)
	at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:168)
	at org.glassfish.grizzly.http.server.HttpHandler.runService(HttpHandler.java:206)
	at org.glassfish.grizzly.http.server.HttpHandler.doHandle(HttpHandler.java:180)
	at org.glassfish.grizzly.http.server.HttpServerFilter.handleRead(HttpServerFilter.java:242)
	at org.glassfish.grizzly.filterchain.ExecutorResolver$9.execute(ExecutorResolver.java:119)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeFilter(DefaultFilterChain.java:284)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.executeChainPart(DefaultFilterChain.java:201)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.execute(DefaultFilterChain.java:133)
	at org.glassfish.grizzly.filterchain.DefaultFilterChain.process(DefaultFilterChain.java:112)
	at org.glassfish.grizzly.ProcessorExecutor.execute(ProcessorExecutor.java:77)
	at org.glassfish.grizzly.nio.transport.TCPNIOTransport.fireIOEvent(TCPNIOTransport.java:539)
	at org.glassfish.grizzly.strategies.AbstractIOStrategy.fireIOEvent(AbstractIOStrategy.java:112)
	at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.run0(WorkerThreadIOStrategy.java:117)
	at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy.access$100(WorkerThreadIOStrategy.java:56)
	at org.glassfish.grizzly.strategies.WorkerThreadIOStrategy$WorkerThreadRunnable.run(WorkerThreadIOStrategy.java:137)
	at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:593)
	at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:573)
	at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.NoSuchMethodError: sun.security.ssl.SSLSessionImpl.<init>(Lsun/security/ssl/ProtocolVersion;Lsun/security/ssl/CipherSuite;Ljava/util/Collection;Lsun/security/ssl/SessionId;Ljava/lang/String;I)V
	at sun.security.ssl.ClientHandshaker.serverHello(ClientHandshaker.java:709)
	at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:209)
	at sun.security.ssl.Handshaker.processLoop(Handshaker.java:984)
	at sun.security.ssl.Handshaker.process_record(Handshaker.java:919)
	at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1064)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1367)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1395)
	at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1379)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:559)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1564)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1492)
	at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:480)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:347)
	at org.glassfish.jersey.client.internal.HttpUrlConnector._apply(HttpUrlConnector.java:390)
	at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:282)
	at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:278)
	... 40 more
]]

上图抛出的异常为Caused by: java.lang.NoSuchMethodError: sun.security.ssl.SSLSessionImpl.<init> 。

我觉得它的意思是SSL session初始化的过程出了问题,接下来我们来看看Wireshark捕捉到的帧:

可以看出, geo.api.gouv.fr (188.165.35.67) 向 SSL client(192.168.0.13) 发送了证书(链), 并且Hello过程结束(Hello Done),

正常情况下,Client应该检测此证书的CA信息,然后与GlassFish里的cacerts.jks的对应CA信息对比,以检验刚发来的证书是否有效。但是Server Hello Done之后ssl的帧已中断,而且Client没有发送含有错误信息的警告帧,说明Client的代码是有问题的。

 

其实问题就出在JDK的jsse.jar包和glassfish自带的grizzly-npn-bootstrap.jar包(版本较旧),这两个包有冲突,我们来看一下:

   

红圈的包名一模一样,而且部分类也一样,所以GlassFish才抛出前面所提的异常。stackoverflow上有人回答了这个问题(感谢^^找了超久)

6.解决办法

  • 把 glassfish5\glassfish\modules\endorsed\grizzly-npn-bootstrap.jar 中的 sun 文件夹删除 (用7zip打开)
  • 替换grizzly-npn-bootstrap.jar 为 grizzly-npn-bootstrap-1.9.jar (目前最新版) 
  • 把JDK降级为jdk8u151.jar (不推荐,但网上很多答案都是这个)

有个小插曲,当看到很多人都说把JDK降级为jdk8u151时,我觉得并不是最优解,所以我打算用最新的jdk8u201并找出了一些关键性的问题,也才促使我写着篇文章。

7.问题二

sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

此时,glassfish说不能找到所请求目标的有效证书路径,翻译过来有些拗口,如果我们看wireshark捕捉的帧,会更清楚。

其中有一条帧的信息为 Alert (Level:Fatal, Description: Certificate Unknown), 这个帧是Client发出的,说明Client 在刚被发来的证书中提取了CA的相关信息,但由于GlassFish中的cacerts.jks没有相对应的CA信息来验证,所以Client发出Certificate Unknown警告。

8.解决办法

把政府服务器相关的CA证书添加到cacerts.jks里,CA root 的证书或者 CA intermediate 的证书都可以。 

政府服务器的证书为 geo.api.gouv.fr,签发此证书的中间CA机构(CA intermediate)的证书为Let's Encrypt Authority X3, 签发中间CA证书的根CA机构(CA root) 的证书为 DST Root CA X3。 

 

 

若要导入证书,则点击“查看证书”,然后点击“详细信息” -> "复制到文件",一般选择DER编码二进制X509格式。

然后用管理员模式打开cmd(命令控制台),输入:

  • cd E:\Javaws\glassfish5\glassfish\domains\domain1\config (进入GlassFish目录的domainX中的config)
  • keytool -importcert -keystore .\cacerts.jks -trustcacerts -alias "letsEncryptX3" -file .\LetsEncryptX3.cer (添加CA证书到cacerts.jks)

然后Client就可以调用政府服务器里的api啦。

 

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论
请先登录 后发表评论~
©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页

打赏作者

龙泽路飞

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值