OkHttp3出现java.io.IOException: Hostname was not verified解决方案

问题

用OkHttp3做https请求时候报了个java.io.IOException: Hostname was not verified的错误。

问题分析

通常是因为SSL协议握手的过程中,这个服务度地址的证书没有被证实,被信任。

报错消息如下。

  Hostname a.com not verified:
    certificate: sha256/s+ZoW0wxlNvmDUguAjYVvc6xxnIetO4XUMissqHkBPg=
    DN: CN=*.b.com, OU=Domain Control Validated
    subjectAltNames: [*.b.com, b.com]

可以看到请求的证书的域名为b.com,而我们要请求的是a.com,因此错误原因是在验证证书时发现真正请求的域名和服务器的证书域名不一致。

解决方案

如果你认为运行的证书没有任何意义,并且想要绕过它们,那么就需要添加一个空主机名验证程序以使后面的请求正常工作。具体代码如下

   public static OkHttpClient.Builder ignoreSSL (OkHttpClient.Builder builder) {
        builder.sslSocketFactory(createSSLSocketFactory())
            .hostnameVerifier((s, sslSession) -> true);
        return builder;
    }

    private static SSLSocketFactory createSSLSocketFactory () {

        SSLSocketFactory sSLSocketFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception e) {
            LOGGER.info(e.getMessage(), e);
        }

        return sSLSocketFactory;
    }

    private static class TrustAllManager implements X509TrustManager {

        @Override
        public void checkClientTrusted (java.security.cert.X509Certificate[] x509Certificates,
            String s) throws java.security.cert.CertificateException {

        }

        @Override
        public void checkServerTrusted (java.security.cert.X509Certificate[] x509Certificates,
            String s) throws java.security.cert.CertificateException {

        }


        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers () {
            return new X509Certificate[0];
        }
    }

自己封装的http工具类

我自己封装的完整的OkHttp3Util工具类代码如下。

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.apache.commons.collections.MapUtils;
import org.apache.http.util.TextUtils;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author magotzis on 2018/7/31 下午5:06
 */
@Slf4j
public class HttpsUtil {

    private static final OkHttpClient client = new OkHttpClient.Builder()
            .readTimeout(10, TimeUnit.SECONDS)
            .writeTimeout(10, TimeUnit.SECONDS)
            .connectTimeout(10, TimeUnit.SECONDS)
            .sslSocketFactory(createSSLSocketFactory())
            .hostnameVerifier((s, sslSession) -> true)
            .build();


    private static final Joiner AMPERSAND_JOINER = Joiner.on("&");

    // 工具类不需要实例化
    private HttpsUtil() {
    }

    private static SSLSocketFactory createSSLSocketFactory () {

        SSLSocketFactory sSLSocketFactory = null;

        try {
            SSLContext sc = SSLContext.getInstance("TLS");
            sc.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom());
            sSLSocketFactory = sc.getSocketFactory();
        } catch (Exception e) {
            log.info(e.getMessage(), e);
        }

        return sSLSocketFactory;
    }

    private static class TrustAllManager implements X509TrustManager {

        @Override
        public void checkClientTrusted (java.security.cert.X509Certificate[] x509Certificates,
                                        String s) throws java.security.cert.CertificateException {
        }

        @Override
        public void checkServerTrusted (java.security.cert.X509Certificate[] x509Certificates,
                                        String s) throws java.security.cert.CertificateException {
        }


        @Override
        public java.security.cert.X509Certificate[] getAcceptedIssuers () {
            return new X509Certificate[0];
        }
    }


    /**
     * 异步调用get请求
     *
     * @param url      请求的url
     * @param callback 回调处理
     */
    public static void getByAsyn(String url, Callback callback) {
        Request request = new Request.Builder()
                .get()
                .url(url)
                .build();

        client.newCall(request).enqueue(callback);
    }

    /**
     * 同步调用get请求
     *
     * @param url 请求的url
     * @return response信息
     * @throws IOException IO异常
     */
    public static Response getBySync(String url) throws IOException {
        Request request = new Request.Builder()
                .get()
                .url(url)
                .build();
        return client.newCall(request).execute();
    }

    /**
     * post请求
     *
     * @param url         请求的url
     * @param requestBody requestBody信息
     * @param headers     请求头部
     * @return response信息
     * @throws IOException IO异常
     */
    public static Response post(String url, RequestBody requestBody, Headers headers) throws IOException {
        Request.Builder build = new Request.Builder();
        if (headers != null) {
            build.headers(headers);
        }
        Request request = build
                .post(requestBody)
                .url(url)
                .build();
        return client.newCall(request).execute();
    }

    /**
     * 拼接url和param
     *
     * @param url    请求的url
     * @param params 需要带的参数
     * @return 拼接好的url
     */
    public static String buildUrl(String url, Map<String, String> params) {
        if (MapUtils.isEmpty(params)) {
            return url;
        }
        List<String> nameValueList = Lists.newArrayListWithExpectedSize(params.size());
        for (Map.Entry<String, String> entry : params.entrySet()) {
            nameValueList.add(entry.getKey() + "=" + entry.getValue());
        }

        return url + "?" + AMPERSAND_JOINER.join(nameValueList);

    }

    /**
     * @param url 下载链接
     * @return 文件路径
     */
    public static File download(String url) {
        Request request = new Request.Builder()
                .url(url)
                .build();
        String fileName;
        File file = null;
        try {
            Response response = client.newCall(request).execute();
            // 解析文件名,如果不存在则随机生成一个uuid作为文件名
            fileName = getHeaderFileName(response);
            if (TextUtils.isEmpty(fileName)) {
                fileName = UidUtil.getUid();
            }
            file = new File(fileName);
            // 输出保存文件
            byte[] buf = new byte[2048];
            int len;
            try (InputStream is = Objects.requireNonNull(response.body()).byteStream();
                 FileOutputStream fos = new FileOutputStream(file)) {
                while ((len = is.read(buf)) != -1) {
                    fos.write(buf, 0, len);
                }
                fos.flush();
            }
        } catch (IOException e) {
            log.error("下载文件失败", e);
        }
        return file;
    }

    /**
     * 解析文件头
     * Content-Disposition: inline; filename="test.pdf";
     * filename*=UTF-8''test.pdf
     */
    private static String getHeaderFileName(Response response) {
        String header = response.header("Content-Disposition");
        if (!TextUtils.isEmpty(header)) {
            header = header.replace("inline;filename=", "");
            header = header.replace("filename*=utf-8", "");
            String[] strings = header.split("; ");
            if (strings.length > 1) {
                header = strings[1].replace("filename=", "");
                header = header.replace("\"", "");
                return header;
            }
            return "";
        }
        return "";
    }

}