欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用RestTemplate调用https接口跳过证书验证

程序员文章站 2022-03-15 11:41:36
目录resttemplate调用https接口跳过证书验证resttemplate访问https遇到ssl证书验证错误添加https证书resttemplate调用https接口跳过证书验证impor...

resttemplate调用https接口跳过证书验证

import javax.net.ssl.hostnameverifier;
import javax.net.ssl.sslcontext;
import javax.net.ssl.sslsession;
import javax.net.ssl.trustmanager;
import javax.net.ssl.x509trustmanager; 
import java.nio.charset.standardcharsets;
import java.security.cert.certificateexception; 
import org.apache.http.conn.ssl.sslconnectionsocketfactory;
import org.apache.http.impl.client.closeablehttpclient;
import org.apache.http.impl.client.httpclients;
import org.springframework.http.client.httpcomponentsclienthttprequestfactory;
import org.springframework.http.converter.stringhttpmessageconverter;
import org.springframework.web.client.resttemplate; 
public class nohttpsclientutils {
 
    /**
     * 跳过证书效验的sslcontext
     * 
     * @return
     * @throws exception
     */
    private static sslcontext createignoreverifyssl() throws exception {
        sslcontext sc = sslcontext.getinstance("tls");
 
        // 实现一个x509trustmanager接口,用于绕过验证,不用修改里面的方法
        x509trustmanager trustmanager = new x509trustmanager() {
            @override
            public void checkclienttrusted(java.security.cert.x509certificate[] paramarrayofx509certificate,
                    string paramstring) throws certificateexception {
            }
 
            @override
            public void checkservertrusted(java.security.cert.x509certificate[] paramarrayofx509certificate,
                    string paramstring) throws certificateexception {
            }
 
            @override
            public java.security.cert.x509certificate[] getacceptedissuers() {
                return null;
            }
        };
        sc.init(null, new trustmanager[] { trustmanager }, null);
        return sc;
    }
 
    /**
     * 构造resttemplate
     * 
     * @return
     * @throws exception
     */
    public static resttemplate getresttemplate() throws exception {
        httpcomponentsclienthttprequestfactory factory = new httpcomponentsclienthttprequestfactory();
        // 超时
        factory.setconnectionrequesttimeout(5000);
        factory.setconnecttimeout(5000);
        factory.setreadtimeout(5000);
        sslconnectionsocketfactory sslsf = new sslconnectionsocketfactory(createignoreverifyssl(),
                // 指定tls版本
                null,
                // 指定算法
                null,
                // 取消域名验证
                new hostnameverifier() {
                    @override
                    public boolean verify(string string, sslsession ssls) {
                        return true;
                    }
                });
        closeablehttpclient httpclient = httpclients.custom().setsslsocketfactory(sslsf).build();
        factory.sethttpclient(httpclient);
        resttemplate resttemplate = new resttemplate(factory);
        // 解决中文乱码问题
        resttemplate.getmessageconverters().set(1, new stringhttpmessageconverter(standardcharsets.utf_8));
        return resttemplate;
    }
}

resttemplate访问https遇到ssl证书验证错误

今天,遇到了一个ssl证书认证的问题,纠结了好久才解决,学到了很多东西,记录下来,分享给大家。

首先说一下大概的背景,我们的项目对接了第三方接口,需要向对方发送https请求。那么问题来了,https请求时需要通过ssl证书认证的,今天流程突然走不下去了,看日志发现是ssl认证的问题:

使用RestTemplate调用https接口跳过证书验证

在网上找资料,解决问题有两个思路:

1.通过相关配置,跳过ssl证书验证,完成请求。

2.下载证书,将证书导入到jdk的证书管理里面。

第一种方法相当于是取巧,逃避问题,我们当然不能那么做了,我们要迎接挑战,所以小编直接pass第一条,选择第二条。

添加https证书

下载证书

先通过浏览器将未签名验证的证书保存到本地, 具体步骤:

使用RestTemplate调用https接口跳过证书验证

点击 不安全–> 证书–> 详细信息 --> 复制到文件 然后默认选择 起一个文件名 , 保存即可。

(mac下载:mac找不到复制的按钮。。。只能另辟蹊径了)点地址栏上的小锁,然后点证书,然后将蓝色的文件拖到要下载的文件夹即可。最好给它重命名方便使用。

导入证书

我们需要将证书导入到jdk的证书管理里面才能咋项目中使用我们刚刚下载的证书。导入命令如下:

keytool -import -noprompt -trustcacerts -alias  -keystore
 /users/zhonghui/develop/jdk-12.0.2.jdk/contents/home/lib/security/cacerts 
-storepass changeit -file eleme.cer

其中,因为我们用的是jdk12,跟常用的jdk8目录是不一样的,所以我的cacerts目录是:/users/zhonghui/develop/jdk-12.0.2.jdk/contents/home/lib/security/cacerts,如果你用的是jdk1.8或者1.7的话,那么你的cacerts在jre下的 /lib/security/下。eleme是我们给复制过来的证书取得别名,eleme.cer就是我们下载改名后的证书。changeit 是密码,如果changeit不行的话,就试试changeme,一般是changeit。

生成keystore文件

keytool -import -storepass changeit -file eleme.cer -keystore eleme.keystore

然后就会生成一个eleme.keystore文件,若权限不够,加sudo即可。将它复制到项目的类路径下。

项目中配置

将下面的resttemplateconfig替换原来的:

package com..xxx.config; 
import java.io.file;
import java.io.fileinputstream;
import java.io.inputstream;
import java.security.keymanagementexception;
import java.security.keystore;
import java.security.keystoreexception;
import java.security.nosuchalgorithmexception;
import java.security.cert.x509certificate;
import java.util.arraylist;
import java.util.list;
 
import org.apache.http.config.registry;
import org.apache.http.config.registrybuilder;
import org.apache.http.conn.socket.connectionsocketfactory;
import org.apache.http.conn.socket.plainconnectionsocketfactory;
import org.apache.http.conn.ssl.noophostnameverifier;
import org.apache.http.conn.ssl.sslconnectionsocketfactory;
import org.apache.http.impl.client.closeablehttpclient;
import org.apache.http.impl.client.httpclients;
import org.apache.http.impl.conn.poolinghttpclientconnectionmanager;
import org.apache.http.ssl.sslcontextbuilder;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.core.io.classpathresource;
import org.springframework.http.client.httpcomponentsclienthttprequestfactory;
import org.springframework.http.converter.httpmessageconverter;
import org.springframework.http.converter.json.mappingjackson2httpmessageconverter;
import org.springframework.http.converter.xml.mappingjackson2xmlhttpmessageconverter;
import org.springframework.web.client.resttemplate; 
import com.alibaba.fastjson.support.spring.fastjsonhttpmessageconverter;
 
@configuration
public class resttemplateconfig { 
    @autowired
    private fastjsonhttpmessageconverter httpmessageconverter;
 
    @bean
    resttemplate resttemplate() throws exception {
    	httpcomponentsclienthttprequestfactory factory = new                                                    
    	        httpcomponentsclienthttprequestfactory();
    	    factory.setconnectionrequesttimeout(5 * 60 * 1000);
    	    factory.setconnecttimeout(5 * 60 * 1000);
    	    factory.setreadtimeout(5 * 60 * 1000);
    	    // https
    	    sslcontextbuilder builder = new sslcontextbuilder();
    	    keystore keystore = keystore.getinstance(keystore.getdefaulttype());
    	    classpathresource resource = new classpathresource("nonghang.keystore");
    	    inputstream inputstream = resource.getinputstream();
    	    keystore.load(inputstream, null);
    	    sslconnectionsocketfactory socketfactory = new sslconnectionsocketfactory(builder.build(), noophostnameverifier.instance);
    	    registry<connectionsocketfactory> registry = registrybuilder.<connectionsocketfactory>create()
    	            .register("http", new plainconnectionsocketfactory())
    	            .register("https", socketfactory).build();
    	    poolinghttpclientconnectionmanager phccm = new poolinghttpclientconnectionmanager(registry);
    	    phccm.setmaxtotal(200);
    	    closeablehttpclient httpclient = httpclients.custom().setsslsocketfactory(socketfactory).setconnectionmanager(phccm).setconnectionmanagershared(true).build();
    	    factory.sethttpclient(httpclient);
    	    resttemplate resttemplate = new resttemplate(factory);
    	    list<httpmessageconverter<?>> converters = resttemplate.getmessageconverters();
            arraylist<httpmessageconverter<?>> convertersvalid = new arraylist<>();
            for (httpmessageconverter<?> converter : converters) {
                if (converter instanceof mappingjackson2httpmessageconverter ||
                    converter instanceof mappingjackson2xmlhttpmessageconverter) {
                    continue;
                }
                convertersvalid.add(converter);
            }
            convertersvalid.add(httpmessageconverter);
            resttemplate.setmessageconverters(convertersvalid);
    	    inputstream.close();
        return resttemplate;
    }
}
 

好啦,万事俱备只欠东风,自信满满的试了一下,发现还是行不通,深受打击的我决定还是去看看导致这类报错的原因。

从根源出发,https的socket工作原理是怎样的?

https的socket工作原理流程图如下:

使用RestTemplate调用https接口跳过证书验证

那么,什么是ssl证书?

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

首先,要知道导致报这个异常的原因不仅仅是因为证书校验不通过。

1.在我们通过https链接服务器时,服务器会给我们返回一个证书,这个证书可能经过ca认证,也可能是未认证的自制证书,客户端拿到这个证书后会对这个证书进行验证,如果是经过ca验证的证书,自然证书校验就能通过,自制证书自然就校验不同过,从而导致上边的异常。

2.证书校验通过后,还需要校验访问的域名是否和证书指定的域名是否匹配。未匹配也会导致如上异常。

3.上边两步都校验通过了才开始进行握手,但握手也有可能失败,从而导致上边的异常。

以上三个步骤中任何一个出了问题,都会连接失败。

首先,我们获取的证书是经过ca认证的,理应不会出现不通过的情况,第三方的域名也是没有问题的。那么到底是哪里出了问题呢?

后来我们从网络组那边了解到,他们那边对最近做了大的改动,之前的proxy都要重新加,加的时候把我们的给漏掉了。。。。辛苦大半天,被坑惨了啊。。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持。