javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated_ssl证书

Hello,小伙伴好久不见!今天在联调接口时,遇到一个错误,纠结了好久才解决.虽然是粗心引起的,但是也大概明白了引起这个错误的来龙去脉,现在整理一下,分享给大家,希望大家在遇到这个类似问题了,不会太无助;

前言

我们来看下问题发生的大背景;这次我们联调是一个获取单笔订单详情的接口,采用https请求方式.我方是请求方,现需请求能力开发平台(对方)的接口

1.Https加密认证过程是什么样的?

我们来看看接口文档的描述(对方视角):
能力开放平台接口传输采用HTTPS加密传输。接口为RESTFul风格的WebService,信息交互过程为用户按我方提供的webservice地址进行调用,我方接到调用请求后,为用户返回JSON格式组织的信息,用户依据约定的接口规范对数据进行解析。它将用户与移动商城开放平台之间传输的内容进行加密认证。使内容的传输处于安全的加密状态,以防止数据在传输中途被窃取。
客户端认证:由能力开放平台为各个商户自有系统颁发客户端的自签发SSL证书和密钥,通过该证书和密钥来访问能力开放平台接口。
能力开放平台使用自身的根证书为商户自有系统颁发客户端证书和密钥:

  • client.crt:客户端证书文件
  • client.key : 客户端密钥文件
  • 客户端证书密码

2.什么是SSL证书?

SSL 证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。

3.环境情况

  • jdk1.7
  • httpclient_4.2.1.jar

一.场景再现

当我们请求对方接口时,发现报了如下错误

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated_sslSocket_02

二.刨根问底

1.温故知新

遇到未见过错误,第一时间拿报错信息去问了度娘,首先发现这位兄台的博客,是我明白了
在我们分析什么原因导致了这个问题之前,我们需要搞明白一件事:https的socket工作原理是怎样的?
https的socket工作原理流程图如下:
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated_httpclient_03
接下来,我们需要搞清楚什么原因会导致这类报错,下面这边博客可以给我们一些思路;peer not authenticated的终极解决方案
在这边文章中,作者介绍可能引发这个报错的几种原因,总结如下:
  首先,要知道导致报这个异常的原因不仅仅是因为证书校验不通过。
  1.在我们通过https链接服务器时,服务器会给我们返回一个证书,这个证书可能经过CA认证,也可能是未认证的自制证书,客户端拿到这个证书后会对这个证书进行验证,如果是经过CA验证的证书,自然证书校验就能通过,自制证书自然就校验不同过,从而导致上边的异常。
  2.证书校验通过后,还需要校验访问的域名是否和证书指定的域名是否匹配。未匹配也会导致如上异常。
  3.上边两步都校验通过了才开始进行握手,但握手也有可能失败,从而导致上边的异常。
  以上三个步骤中任何一个出了问题,都会连接失败。
###2.问题探究
根据报错信息的堆栈信息,我去跟踪了源码,发现在错误的发送在SSLSessionImpl类的下面方法中
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated_httpclient_04

public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
    if(this.cipherSuite.keyExchange != KeyExchange.K_KRB5 && this.cipherSuite.keyExchange != KeyExchange.K_KRB5_EXPORT) {
        if(this.peerCerts == null) {
            throw new SSLPeerUnverifiedException("peer not authenticated");
        } else {
            return (Certificate[])((Certificate[])this.peerCerts.clone());
        }
    } else {
        throw new SSLPeerUnverifiedException("no certificates expected for Kerberos cipher suites");
    }
}

根据如上源码,我们发现只有peerCerts==null时候,才导致了报错.继续跟踪源码,我们发现下面的方法会给他赋值
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated_ssl证书_05

void setPeerCertificates(X509Certificate[] var1) {
    if(this.peerCerts == null) {
        this.peerCerts = var1;
    }

}

现在有一个问题,什么时候我们会调用这个方法去给他赋值?这个问题困扰了好久,直到看到这位大佬的文章:jdk6出现peer not authenticated问题原因分析与解决
在这篇文章中,我们发现peerCerts是用来存储服务端返回的证书;附上文章中作者的一段代码
1.httpclient未获取到服务器证书
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated_httpclient_06
2.httpclient获取到了服务器证书
javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated_httpclient_07
这样,我们就基本定位问题原因:没有获取到服务端证书;

三.解决方案

当我们去搜这个问题解决方案时,会发现很多都是重写底层的验证方法来绕过校验,可能这种解决思路适合需要发起https请求,但是没有证书的情况;
因为本次接口调用时一定需要证书的,所以这类解决思路并不适合我,定位了问题的原因以后,经过排除居然是请求地址未加端口号,导致无法获取到服务端证书;

重要的事情说三遍:细心,细心,细心!!!