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中,
网友评论