1.前言
HTTPS (全称:Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 [1] 。HTTPS 在HTTP 的基础下加入SSL 层,HTTPS 的安全基础是 SSL,因此加密的详细内容就需要 SSL。 HTTPS 存在不同于 HTTP 的默认端口及一个加密/身份验证层(在 HTTP与 TCP 之间)。这个系统提供了身份验证与加密通讯方法。
JDK中keytool 常用命令:
-genkey 在用户主目录中创建一个默认文件".keystore",还会产生一个mykey的别名,mykey中包含用户的公钥、私钥和证书
(在没有指定生成位置的情况下,keystore会存在用户系统默认目录,如:对于window xp系统,会生成在系统的C:/Documents and Settings/UserName/文件名为“.keystore”)
-alias 产生别名
-keystore 指定密钥库的名称(产生的各类信息将不在.keystore文件中)
-keyalg 指定密钥的算法 (如 RSA DSA(如果不指定默认采用DSA))
-validity 指定创建的证书有效期多少天
-keysize 指定密钥长度
-storepass 指定密钥库的密码(获取keystore信息所需的密码)
-keypass 指定别名条目的密码(私钥的密码)
-dname 指定证书拥有者信息 例如: "CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码"
-list 显示密钥库中的证书信息 keytool -list -v -keystore 指定keystore -storepass 密码
-v 显示密钥库中的证书详细信息
-export 将别名指定的证书导出到文件 keytool -export -alias 需要导出的别名 -keystore 指定keystore -file 指定导出的证书位置及证书名称 -storepass 密码
-file 参数指定导出到文件的文件名
-delete 删除密钥库中某条目 keytool -delete -alias 指定需删除的别 -keystore 指定keystore -storepass 密码
-printcert 查看导出的证书信息 keytool -printcert -file yushan.crt
-keypasswd 修改密钥库中指定条目口令 keytool -keypasswd -alias 需修改的别名 -keypass 旧密码 -new 新密码 -storepass keystore密码 -keystore sage
-storepasswd 修改keystore口令 keytool -storepasswd -keystore e:/yushan.keystore(需修改口令的keystore) -storepass 123456(原始密码) -new yushan(新密码)
-import 将已签名数字证书导入密钥库 keytool -import -alias 指定导入条目的别名 -keystore 指定keystore -file 需导入的证书
本文使用okhttp3访问SpringBoot创建的https接口。
2.SpringBoot配置文件
applicathon.properties
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:starpulse_server.p12
server.ssl.key-password=xxxxx
server.ssl.key-store-password=xxxxx
server.ssl.key-alias=starpulse_server
server.ssl.trust-store=classpath:starpulse_server.p12
server.ssl.trust-store-password=xxxxx
server.ssl.trust-store-provider=SUN
server.ssl.client-auth=need
3.代码逻辑
3.1 公共实体类&KeyTools工具类
KeyStoreCommand .java
public class KeyStoreCommand {
private String alias;//产生别名
private int keysize;//指定密钥长度
private String keyalg; //指定密钥的算法 (如 RSA DSA(如果不指定默认采用DSA))
private String sigalg;// 签名算法名称
private String destalias; //目标别名
private String startdate;//证书有效期开始日期/时间
private String commonname;//CN=名字与姓氏
private String organizationalUnit;//OU=组织单位名称
private String organization;//O=组织名称
private String city;//L=城市或区域名称
private String state;//ST=州或省份名称
private String country;//C=单位的两字母国家代码
private long validity; //指定创建的证书有效期多少天
private String keystore;// 指定密钥库的名称
private String storepass;// 指定密钥库的密码(获取keystore信息所需的密码)
private String keypass;//指定别名条目的密码(私钥的密码)
private String storetype;//密钥库类型
private String filepath;// 参数指定导出到文件的证书文件名
Keytools.java类
//1.使用java代码生成密钥库
public static KeyStore createKeyStoreFile(KeyStoreCommand entity,String serverAlias,Certificate serverCert) throws Exception {
final String alias = entity.getAlias();//"home2";
String keystore = entity.getKeystore();//"d:/keys/home2.p12";
final int keySize = entity.getKeysize(); //1024;
final String keyalg=entity.getKeyalg();//RSA
final String sigalg=entity.getSigalg();//SHA256withRSA
final String commonName = entity.getCommonname();//"db";
final String organizationalUnit = entity.getOrganizationalUnit();//"com.home";
final String organization = entity.getOrganization();//"easywith";
final String city = entity.getCity();//"guiyang";
final String state = entity.getState();//"guizhou"
final String country = entity.getCountry();//"cn"
final long validity = entity.getValidity();//3650
final String keyPassword = entity.getKeypass();//"123456";
final String storetype = entity.getStoretype();//PKCS12
// keytool工具
CertAndKeyGen keyGen = new CertAndKeyGen(keyalg, sigalg);
// 通用信息
X500Name x500Name = new X500Name(commonName, organizationalUnit, organization, city, state, country);
//根据密钥长度生成公钥和私钥
keyGen.generate(keySize);
PrivateKey privateKey = keyGen.getPrivateKey();
// 证书
X509Certificate certificate = keyGen.getSelfCertificate(x500Name, new Date(), (long) validity * 24 * 60 * 60);
KeyStore keyStore = KeyStore.getInstance(storetype);
keyStore.load(null,null);
keyStore.setKeyEntry(alias,privateKey,keyPassword.toCharArray(),new Certificate[]{certificate});
System.out.println("Assigns the given trusted certificate to the given alias");
if(StringUtils.isNotEmpty(serverAlias)){
keyStore.setCertificateEntry(serverAlias,serverCert);
}
FileOutputStream outputStream = new FileOutputStream(keystore);
keyStore.store(outputStream,keyPassword.toCharArray());
outputStream.close();
System.out.println("keyStore file created ...");
return keyStore;
}
3.2 构造实体对象
3.2.1 构造KeyStoreCommand实例对象
HanyuPinyinOutputFormat format = new HanyuPinyinOutputFormat();
//拼音小写
format.setCaseType(HanyuPinyinCaseType.LOWERCASE);
//不带声调
format.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
//要转换中文格式
String clientAlias = PinyinHelper.toHanYuPinyinString(tenant.getFullName(), format, "", true);
//每次更新秘钥及证书文件版本递增(alias_0,alias_1....)
clientAlias = clientAlias+"_"+tenant.getVersion();
Long validity = ((tenant.getExpaireTime().getTime() - tenant.getCreateTime().getTime()) / 86400000L);
log.info("受信任证书有效天数:{}(单位:天)",validity);
String CN = (tenant.getShortName()),
OU = (tenant.getFullName()),
O = (tenant.getShortName()),
L = (PinyinHelper.toHanYuPinyinString(tenant.getCity(), format, "", true)),
ST = (PinyinHelper.toHanYuPinyinString(tenant.getProvince(), format, "", true)),
C = ("CN");
log.info("CN=(名字与姓氏):{}, OU=(组织单位名称):{}, O=(组织名称):{}," +
" L=(城市或区域名称):{}, ST=(州或省份名称):{}, C=(单位的两字母国家代码):{}",
CN,OU,O,L,ST,C);
String keyStore = rootPath + clientAlias+".p12";
String cerPath = rootPath + clientAlias+".cer";
log.info("客户端秘钥库:{},受信任证书库:{}",keyStore,cerPath);
3.2.2 重点:核心操作(证书交换)双方受信任
总体步骤:
1. 创建服务端keystore秘钥库
2. 从服务端keystore秘钥库导出服务端证书
3. 创建客户端keystore秘钥库
4. 从客户端keystore秘钥库导出客户端证书
5. 将服务端证书导入客户端keystore中
6. 将客户端证书导入服务端keystore中
首先提前创建好服务端keystore并导出服务端证书(两种方式) 1.通过keytool命令行方式来创建
1.生成服务端keystore
keytool -genkeypair -alias server -keyalg RSA -sigalg SHA256withRSA -dname CN="xxxxx公司",OU="易xx" ,O="大数据组" ,L=Guiyang,ST=Guizhou,C=CN -validity 3650 -keypass 123456-storepass xxxxxx -storetype PKCS12 -keystore D:/keys/starpulse_server.p12
2.导出服务端证书
keytool -export -alias server -file D:/keys/starpulse_server.cer -keystore D:/keys/starpulse_server.p12 -storetype PKCS12 -storepass 123456
2.根据java代码来创建
public static KeyStore createKeyStoreFile() throws Exception {
final String alias = entity.getAlias();
String keystore = entity.getKeystore();
final int keySize = entity.getKeysize();
final String keyalg=entity.getKeyalg();
final String sigalg=entity.getSigalg();
final String commonName = entity.getCommonname();
final String organizationalUnit = entity.getOrganizationalUnit();
final String organization = entity.getOrganization();
final String city = entity.getCity();
final String state = entity.getState();
final String country = entity.getCountry();
final long validity = entity.getValidity();
final String keyPassword = entity.getKeypass();
final String storetype = entity.getStoretype();
CertAndKeyGen keyGen = new CertAndKeyGen(keyalg, sigalg);
X500Name x500Name = new X500Name(commonName, organizationalUnit, organization, city, state, country);
keyGen.generate(keySize);
PrivateKey privateKey = keyGen.getPrivateKey();
X509Certificate certificate = keyGen.getSelfCertificate(x500Name, new Date(), (long) validity * 24 * 60 * 60);
KeyStore keyStore = KeyStore.getInstance(storetype);
keyStore.load(null,null);
keyStore.setKeyEntry(alias,privateKey,keyPassword.toCharArray(),new Certificate[]{certificate});
FileOutputStream outputStream = new FileOutputStream(keystore);
keyStore.store(outputStream,keyPassword.toCharArray());
outputStream.close();
System.out.println("keyStore file created ...");
}
public static void exportCert(KeyStore keystore, String alias, String exportFile) throws Exception {
Certificate cert = keystore.getCertificate(alias);
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(cert.getEncoded());
FileWriter fw = new FileWriter(exportFile);
fw.write("-----BEGIN CERTIFICATE-----\r\n");
fw.write(encoded);
fw.write("\r\n-----END CERTIFICATE-----");
fw.close();
}
核心操作(证书交换)双方受信任
//第一步:加载服务端秘钥库
KeyStore serverKeystore = KeyStore.getInstance("PKCS12");
serverKeystore.load(new FileInputStream(new File(serverStore)), password.toCharArray());
Certificate serverCert = serverKeystore.getCertificate(serverAlias);
//第二步:创建客户端秘钥库(详见keytools工具类)
KeyStore clientKeystore = KeyTools.createKeyStoreFile(entity,serverAlias,serverCert);
KeyTools.exportCert(clientKeystore, entity.getAlias(),entity.getFilepath());
log.info("客户信任证书:{}制发完成",entity.getFilepath());
Certificate clientCert = clientKeystore.getCertificate(entity.getAlias());
log.info("根据alias导出证书客户指纹信任证书:{}",entity.getAlias());
//第三步:导入客户端证书到服务端受信库(让服务端信任客户端)
serverKeystore.setCertificateEntry(entity.getAlias(),clientCert);
//第四步:将服务端秘钥保存到文件
FileOutputStream outputStream = new FileOutputStream(serverStore);
serverKeystore.store(outputStream,password.toCharArray());
log.info("客户:{}信任证书,alias:{}制发完成",entity.getCommonname(),entity.getAlias());
运行秘钥及证书结果如下 导出证书生成功能完成
4.示例代码使用okhttp3访问https接口
使用okhttp3访问SpringBoot创建的https接口 明天写吧,今天太累了。。。。 我回来了2022年3月19日15:02:51
代码调整了一上午,终于通了,期间一系列问题都排除了,把结果记录在这
这里是keytool生成服务端秘钥库的指令
1.生成服务端keystore
keytool -genkeypair -alias starpulse_server -keyalg RSA -sigalg SHA256withRSA -dname CN="xxxxx公司,OU=易 ,O=大数据组 ,L=Guiyang,ST=Guizhou,C=CN" -validity 3650 -keypass xxxxx -storepass xxxxx -storetype PKCS12 -keystore D:/keys/starpulse_server.p12
2.导出服务端证书
keytool -export -alias starpulse_server -file D:/keys/starpulse_server.cer -keystore D:/keys/starpulse_server.p12 -storetype PKCS12 -storepass xxxxx
3.查看证书指纹
keytool -list -keystore d:/keys/starpulse_server.p12 -storepass easywith2022
4.1 拷贝证书到resources目录下
本文使用okhttp3访问SpringBoot创建的https接口 将生成的证书文件拷贝到项目工程resources目录下,与配置文件中路径一致 如下图所示:
4.2 监听到http的端口号后转向到的https的端口号
TomcatHttpConfig.java
package cn.easywith.orm.config;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class TomcatHttpConfig {
/**
* http重定向到https
*
* @return
*/
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
};
tomcat.addAdditionalTomcatConnectors(httpConnector());
return tomcat;
}
@Bean
public Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
//Connector监听的http的端口号
connector.setPort(80);
connector.setSecure(false);
//监听到http的端口号后转向到的https的端口号
connector.setRedirectPort(443);
return connector;
}
}
4.3 https双向认证代码测试类
编写测试API接口TestController.java
package cn.easywith.orm.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@RestController
@RequestMapping("/test/*")
public class TestController {
@RequestMapping("sample")
public Object getSample() {
return "123346";
}
@RequestMapping("/hi")
public ModelAndView sayHello() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("hello");
modelAndView.addObject("key", 12345);
//System.out.println("test");
return modelAndView;
}
}
package cn.easywith.test;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import javax.net.ssl.*;
import org.springframework.context.annotation.Bean;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
public class test {
private static final String PROTOCOL = "TLS";
private static final String KEY_KEYSTORE_TYPE = "PKCS12";
private static final String p12Path = "D:\\keys\\xunzhaoshouyiren_0.p12";
private static final String cerPath = "D:\\keys\\starpulse_server.cer";
private static final String appKey = "17a61a300fe641fbb982de7ec98278d4";
private static KeyManager[] getKeyManagers() throws Exception {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE);
InputStream inputStream = new FileInputStream(new File(p12Path));
keyStore.load(inputStream, appKey.toCharArray());
keyManagerFactory.init(keyStore, appKey.toCharArray());
KeyManager[] keyManagers = keyManagerFactory.getKeyManagers();
return keyManagers;
}
private static TrustManager[] getTrustManagers() {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
return trustAllCerts;
}
public static X509TrustManager getX509TrustManager() {
X509TrustManager trustManager = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KEY_KEYSTORE_TYPE);
InputStream inputStream = new FileInputStream(new File(cerPath));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate ca = certificateFactory.generateCertificate(inputStream);
keyStore.load(null, null);
keyStore.setCertificateEntry("starpulse_server", ca);
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
}
trustManager = (X509TrustManager) trustManagers[0];
} catch (Exception e) {
e.printStackTrace();
}
return trustManager;
}
private static SSLContext getSslContext(KeyManager[] keyManagers, TrustManager[] trustManagers) throws Exception {
SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
sslContext.init(keyManagers, trustManagers, new SecureRandom());
return sslContext;
}
public static SSLSocketFactory sslSocketFactory(){
SSLSocketFactory socketFactory = null;
try {
KeyManager[] keyManagers = getKeyManagers();
TrustManager[] trustManagers = getTrustManagers();
SSLContext sslContext = getSslContext(keyManagers, trustManagers);
socketFactory = sslContext.getSocketFactory();
}catch (Exception e) {
e.printStackTrace();
}
return socketFactory;
}
@Bean
public static ConnectionPool pool() {
return new ConnectionPool(200, 5, TimeUnit.MINUTES);
}
public static void main(String[] args) throws Exception {
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory(), getX509TrustManager())
.retryOnConnectionFailure(false)
.connectionPool(pool())
.connectTimeout(10L, TimeUnit.SECONDS)
.readTimeout(10L, TimeUnit.SECONDS)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
System.out.println("主机:"+hostname);
return true;
}
})
.build();
Request request = new Request.Builder()
.url("https://localhost:8443/test/hi")
.header("User-Agent", "OkHttp Headers.java")
.build();
Response response = client.newCall(request).execute();
String result = response.body().string();
System.out.println(result);
}
}
请求路径为普通http时: http://localhost:8443/test/hi 会提示:主机和端口的这种组合需要TLS 请求路径为普通https时: https://localhost:8443/test/hi 此时,请求正常返回 到此,使用okhttp3访问SpringBoot创建的https接口,全部结束了
5.浏览器登录效果展现
第一步:在浏览输入请求地址会发现无法访问 https://localhost:8443/test/hi 此网站无法提供安全连接 localhost 不接受您的登录证书,或者您可能没有提供登录证书。 第二步:导入我们的证书
此服务器无法证实它就是 localhost - 它的安全证书没有指定主题备用名称。这可能是因为某项配置有误或某个攻击者拦截了您的连接。
发现可以正常访问了
|