Java实现Https调用

2021-04-06  本文已影响0人  lgtn

https的服务器配置见:https://www.jianshu.com/p/860a297e1323

import java.security.cert.CertificateException;

import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.ctjsoft.ticket.https.example.util.CertificationUtil;

/**

* 证书认证管理,实现X509TrustManager接口

*/

public class MyX509TrustManager implements X509TrustManager{

private static final LoggerLOGGER = LoggerFactory.getLogger(MyX509TrustManager.class);

  private static final X509Certificate[]EMPTY_X509CERTIFICATE_ARRAY =new X509Certificate[]{};

  private X509Certificate[]rootCerts;

  public MyX509TrustManager(X509Certificate rootCertificate) {

if(rootCertificate ==null) {

throw new IllegalArgumentException("root certificate is null ");

      }

LOGGER.info("init root certificate");

      this.rootCerts =new X509Certificate[]{rootCertificate};

  }

public MyX509TrustManager(X509Certificate[] rootCerts) {

if(rootCerts ==null || rootCerts.length ==0) {

throw new IllegalArgumentException("root certificate is null ");

      }

LOGGER.info("init root certificates ,sizei:{}",rootCerts.length);

      this.rootCerts = rootCerts;

  }

@Override

  public void checkClientTrusted(X509Certificate[] arg0, String arg1)throws CertificateException {

System.out.println("checkClientTrusted:"+arg1);

  }

@Override

    public void checkServerTrusted(X509Certificate[] chain, String authType)throws CertificateException {

if (chain ==null) {

throw new CertificateException("checkServerTrusted: X509Certificate array is null");

        }

if (chain.length <1) {

throw new CertificateException("checkServerTrusted: X509Certificate is empty");

        }

if (!(null != authType && authType.equals("ECDHE_RSA"))) {

throw new CertificateException("checkServerTrusted: AuthType is not ECDHE_RSA");

        }

      //验证证书

       boolean isTrusted = CertificationUtil.verifyCertChain(chain,rootCerts);

      if(!isTrusted) {

throw new CertificateException("server's Cert verify fail");

      }

}

@Override

  public X509Certificate[]getAcceptedIssuers() {

return EMPTY_X509CERTIFICATE_ARRAY;

  }

}

import java.io.FileInputStream;

import java.io.InputStream;

import java.nio.file.Files;

import java.nio.file.Path;

import java.nio.file.Paths;

import java.security.InvalidKeyException;

import java.security.NoSuchAlgorithmException;

import java.security.NoSuchProviderException;

import java.security.Principal;

import java.security.PublicKey;

import java.security.SignatureException;

import java.security.cert.CertificateException;

import java.security.cert.CertificateExpiredException;

import java.security.cert.CertificateFactory;

import java.security.cert.CertificateNotYetValidException;

import java.security.cert.X509Certificate;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class CertificationUtil {

private static final LoggerLOGGER = LoggerFactory.getLogger(CertificationUtil.class);

  /**

* 加载证书

    * @param inputStream

    * @return

    */

  public static X509CertificateloadCertificate(String certPath) {

try  {

Path path = Paths.get("src/main/resources", certPath);

            InputStream inputStream =new FileInputStream(path.toFile());

          CertificateFactory cf = CertificateFactory.getInstance("X509");

              X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);

              cert.checkValidity();

          return cert;

        }catch (CertificateExpiredException e) {

throw new RuntimeException("证书已过期", e);

      }catch (CertificateNotYetValidException e) {

throw new RuntimeException("证书尚未生效", e);

      }catch (CertificateException e) {

throw new RuntimeException("无效的证书", e);

      }catch (Exception e) {

String errorMessage = e.getMessage() ==null ?"" : e.getMessage() +"。";

            if (certPath.startsWith("/")) {

errorMessage +="ClassPath路径不可以/开头,请去除后重试。";

            }

throw new RuntimeException("读取[" + certPath +"]失败。" + errorMessage, e);

        }

}

/**

* 验证证书链是否是信任证书库中证书签发的

*

    * @param certs    目标验证证书列表

    * @param rootCerts 可信根证书列表

    * @return 验证结果

*/

    public static boolean verifyCertChain(X509Certificate[] certs, X509Certificate[] rootCerts) {

boolean sorted =sortByDn(certs);

        if (!sorted) {

LOGGER.error("证书链验证失败:不是完整的证书链");

return false;

        }

//先验证第一个证书是不是信任库中证书签发的

        X509Certificate prev = certs[0];

        boolean firstOK =verifyCert(prev, rootCerts);

        if (!firstOK || certs.length ==1) {

return firstOK;

        }

//验证证书链

        for (int i =1; i < certs.length; i++) {

try {

X509Certificate cert = certs[i];

                try {

cert.checkValidity();

                }catch (CertificateExpiredException e) {

LOGGER.error("证书已经过期",e);

return false;

                }catch (CertificateNotYetValidException e) {

LOGGER.error("证书未激活",e);

return false;

                }

verifySignature(prev.getPublicKey(), cert);

                prev = cert;

            }catch (Exception e) {

LOGGER.error("证书链验证失败",e);

return false;

            }

}

return true;

    }

/**

* 验证证书是否是信任证书库中证书签发的

*

    * @param cert      目标验证证书

    * @param rootCerts 可信根证书列表

    * @return 验证结果

*/

    private static boolean verifyCert(X509Certificate cert, X509Certificate[] rootCerts) {

try {

cert.checkValidity();

        }catch (CertificateExpiredException e) {

LOGGER.error("证书已经过期", e);

return false;

        }catch (CertificateNotYetValidException e) {

LOGGER.error("证书未激活", e);

return false;

        }

Map subjectMap =new HashMap();

        for (X509Certificate root : rootCerts) {

subjectMap.put(root.getSubjectDN(), root);

        }

Principal issuerDN = cert.getIssuerDN();

        X509Certificate issuer = subjectMap.get(issuerDN);

        if (issuer ==null) {

LOGGER.error("证书链验证失败");

return false;

        }

try {

PublicKey publicKey = issuer.getPublicKey();

            verifySignature(publicKey, cert);

        }catch (Exception e) {

LOGGER.error("证书链验证失败", e);

return false;

        }

return true;

    }

private static void verifySignature(PublicKey publicKey, X509Certificate cert)

throws NoSuchProviderException, CertificateException, NoSuchAlgorithmException, InvalidKeyException,

            SignatureException {

cert.verify(publicKey);

    }

private static boolean sortByDn(X509Certificate[] certs) {

//主题和证书的映射

        Map subjectMap =new HashMap();

        //签发者和证书的映射

        Map issuerMap =new HashMap();

        //是否包含自签名证书

        boolean hasSelfSignedCert =false;

        for (X509Certificate cert : certs) {

if (isSelfSigned(cert)) {

if (hasSelfSignedCert) {

return false;

                }

hasSelfSignedCert =true;

            }

Principal subjectDN = cert.getSubjectDN();

            Principal issuerDN = cert.getIssuerDN();

            subjectMap.put(subjectDN, cert);

            issuerMap.put(issuerDN, cert);

        }

List certChain =new ArrayList();

        X509Certificate current = certs[0];

        addressingUp(subjectMap, certChain, current);

        addressingDown(issuerMap, certChain, current);

        //说明证书链不完整

        if (certs.length != certChain.size()) {

return false;

        }

//将证书链复制到原先的数据

        for (int i =0; i < certChain.size(); i++) {

certs[i] = certChain.get(i);

        }

return true;

    }

/**

* 向上构造证书链

*

    * @param subjectMap 主题和证书的映射

    * @param certChain  证书链

    * @param current    当前需要插入证书链的证书,include

*/

    private static void addressingUp(final Map subjectMap, List certChain,

                                    final X509Certificate current) {

certChain.add(0, current);

        if (isSelfSigned(current)) {

return;

        }

Principal issuerDN = current.getIssuerDN();

        X509Certificate issuer = subjectMap.get(issuerDN);

        if (issuer ==null) {

return;

        }

addressingUp(subjectMap, certChain, issuer);

    }

/**

* 向下构造证书链

*

    * @param issuerMap 签发者和证书的映射

    * @param certChain 证书链

    * @param current  当前需要插入证书链的证书,exclude

*/

    private static void addressingDown(final Map issuerMap, List certChain,

                                      final X509Certificate current) {

Principal subjectDN = current.getSubjectDN();

        X509Certificate subject = issuerMap.get(subjectDN);

        if (subject ==null) {

return;

        }

if (isSelfSigned(subject)) {

return;

        }

certChain.add(subject);

        addressingDown(issuerMap, certChain, subject);

    }

/**

* 验证证书是否是自签发的

*

    * @param cert 目标证书

    * @return true;自签发,false;不是自签发

*/

    private static boolean isSelfSigned(X509Certificate cert) {

return cert.getSubjectDN().equals(cert.getIssuerDN());

    }

}


static SSLSocketFactorygetSSLSocketFactory()throws NoSuchAlgorithmException, KeyManagementException {

SSLContext ctx =null;

      ctx = SSLContext.getInstance("TLS");

      X509Certificate rootCertificate = CertificationUtil.loadCertificate("merrick2.crt");

      ctx.init(null, new TrustManager[] {new MyX509TrustManager(rootCertificate) }, new SecureRandom());

      return ctx.getSocketFactory();

  }

 一、JDK ---单向https

private static String doPost(String httpUrl,String param) throws NoSuchAlgorithmException, KeyManagementException {

    HttpsURLConnection connection =null;

      InputStream is =null;

      OutputStream os =null;

      BufferedReader br =null;

        String result =null;

        try {

            URL url =new URL(httpUrl);

            // 通过远程url连接对象打开连接

            connection = (HttpsURLConnection) url.openConnection();

            connection.setSSLSocketFactory(getSSLSocketFactory());

            connection.setHostnameVerifier(new DefaultHostnameVerifier());

            // 设置连接请求方式

            connection.setRequestMethod(ExampleConstants.REQUEST_METHOD);

            // 设置连接主机服务器超时时间:60000毫秒

            connection.setConnectTimeout(2000);

            // 设置读取主机服务器返回数据超时时间:60000毫秒

            connection.setReadTimeout(60000);

            // 默认值为:false,当向远程服务器传送数据/写数据时,需要设置为true

            connection.setDoOutput(true);

            // 默认值为:true,当前向远程服务读取数据时,设置为true,该参数可有可无

            connection.setDoInput(true);

            // 设置传入参数的格式:请求参数应该是 name1=value1&name2=value2 的形式。

            connection.setRequestProperty("Content-Type", ExampleConstants.Content_Type);

            // 通过连接对象获取一个输出流

            os = connection.getOutputStream();

            // 通过输出流对象将参数写出去/传输出去,它是通过字节数组写出的

            os.write(param.getBytes());

            // 通过连接对象获取一个输入流,向远程读取

            if (connection.getResponseCode() ==200) {

                is = connection.getInputStream();

                // 对输入流对象进行包装:charset根据工作项目组的要求来设置

                br =new BufferedReader(new InputStreamReader(is, ExampleConstants.DEFAULT_CHARSET));

                StringBuffer sbf =new StringBuffer();

                String temp =null;

                // 循环遍历一行一行读取数据

                while ((temp = br.readLine()) !=null) {

sbf.append(temp);

                    sbf.append("\r\n");

                }

result = sbf.toString();

            }

}catch (MalformedURLException e) {

e.printStackTrace();

        }catch (IOException e) {

e.printStackTrace();

        }finally {

// 关闭资源

            if (null != br) {

try {

br.close();

                }catch (IOException e) {

e.printStackTrace();

                }

}

if (null != os) {

try {

os.close();

                }catch (IOException e) {

e.printStackTrace();

                }

}

if (null != is) {

try {

is.close();

                }catch (IOException e) {

e.printStackTrace();

                }

}

// 断开与远程地址url的连接

            connection.disconnect();

        }

return result;

    }

二、OKhttp---单向https

public static Response httpsPost(String url, String json)throws Exception {

OkHttpClient client =new OkHttpClient.Builder().

sslSocketFactory(getSSLSocketFactory()).

//解决报错javax.net.ssl.SSLPeerUnverifiedException: Hostname 127.0.0.1 not verified

                    hostnameVerifier(new HostnameVerifier() {

@Override

                public boolean verify(String s, SSLSession sslSession) {

                System.out.println("主机:" + s);

                return true;

                }

}).

connectTimeout(10, TimeUnit.MINUTES).

readTimeout(10,TimeUnit.MINUTES).

build();

    RequestBody body = RequestBody.create(JSON, json);

    Request request =new Request.Builder()

.url(url)

.post(body)

.build();

    Response response = client.newCall(request).execute();

    return response;

}

三、双向认证

如果要开启https双向认证:

(1)nginx中增加配置:由nginx去对客户端证书的验证

ssl_verify_client:on  # on-开启证书校验;off:关闭,此配置默认为关闭

ssl_client_certificate:     #客户端 根级证书公钥所在路径

(2)客户端代码改造:

在单向https的代码基础上,增加客户端证书(pfx格式,包含公钥、私钥)

第一种办法: MyX509TrustManager中传入两个证书

第二种办法:MyX509TrustManager使用KeyStore,将证书放入KeyStore中,

上一篇 下一篇

猜你喜欢

热点阅读