前提
为了迎合等保要求,本人秉承着安全(偷懒)的意识,决定在接手开发的短信服务上集成https
。这里以springboot
项目为例,通篇以流水账的形式,主要讲述如何生成证书、项目中如何集成以及对外接口如何调用等等。
<!-- more -->
获取证书
进入JAVA_HOME/bin
目录下面,cmd
执行如下命令
keytool -genkey -alias zkcsdn -keypass zsrj@2022 -keyalg RSA -keysize 2048 -validity 3650 -keystore zkcsdn.p12 -storepass zsrj@2022 -deststoretype pkcs12 -alias 别名 -keyalg 算法类型 -keysize 密码长度 -keypass 私钥访问密码 -validity 证书有效期 -keystore 证书名称 -storepass keystone文件访问密码
回车后,按提示输入下发信息,即可在执行目录下面生成p12
证书文件
项目集成
springboot
项目集成证书很简单,p12
证书放置在项目resource
目录下,然后仅需在application.yml
中配置以下参数即可
server: ssl: # 证书路径 key-store: classpath:zkcsdn.p12 # 证书密码 key-store-password: zsrj@2022 # 证书类型 key-store-type: PKCS12 # 证书别名 key-alias: zkcsdn
对外接口调用
由于我们做了https
升级改造,且是自签名的证书,所以浏览器访问时会显示不受信任。这时候用后台调用接口会报错
所以我们客户端在接口调用时也需要集成证书, 这里以主流的OkHttpClient
为例,SSL
证书配置示例
@Bean public OkHttpClient okHttpClient(@Autowired SmsProperty smsProperty) { // 创建连接池,设置最大空闲连接数和连接保持时间 ConnectionPool connectionPool = new ConnectionPool(smsProperty.getMaxIdleConnections() , smsProperty.getKeepAliveDuration(), TimeUnit.MINUTES); // 创建OkHttpClient并设置连接池 OkHttpClient.Builder builder = new OkHttpClient.Builder() .connectionPool(connectionPool); if (smsProperty.isSslEnable()) { TrustManagerFactory factory = createTrustManagerFactory(); SSLSocketFactory sslSocketFactory = createSSLSocketFactory(factory); // 将 SSL socket 工厂和 X509TrustManager 添加到 OkHttpClient 的构建器中 builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) factory.getTrustManagers()[0]) .hostnameVerifier((hostname, session) -> smsProperty.getUrl().contains(hostname)); } return builder.build(); } private static TrustManagerFactory createTrustManagerFactory() { // 从类路径中加载 P12 证书 KeyStore keyStore = loadP12CertificateFromResource(); // 创建一个信任管理器,用于信任 KeyStore 中的证书 TrustManagerFactory trustManagerFactory; try { trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); } catch (NoSuchAlgorithmException | KeyStoreException e) { throw new RuntimeException(e); } return trustManagerFactory; } private static SSLSocketFactory createSSLSocketFactory(TrustManagerFactory factory) { // 创建一个 SSL 上下文,使用信任管理器 SSLContext sslContext; try { sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, factory.getTrustManagers(), null); } catch (NoSuchAlgorithmException | KeyManagementException e) { throw new RuntimeException(e); } return sslContext.getSocketFactory(); } private static KeyStore loadP12CertificateFromResource() { try (InputStream certInputStream = new FileInputStream("证书路径")) { KeyStore keyStore = KeyStore.getInstance("PKCS12"); keyStore.load(certInputStream, "证书密码"); return keyStore; } catch (Exception e) { throw new RuntimeException(e); } }
其他问题(FAQ)
SSL秘钥认证异常
JDK 8u301
以前的版本中,默认情况下不支持PKCS12
格式的密钥库,需要在代码中显式地注册PKCS12
密钥库,否则会报错:parseAlgParameters failed: Objectidentifier() -- data isn't an object ID (tag = 48)
所以一般我们推荐jdk
升级至JDK 8u301
及以上版本
其他客户端集成
我们都知道p12
证书仅限于java
应用的集成开发,jdk
通过java.security.KeyStore
类加载和管理P12
证书。如果我们对接的第三方应用是其他编程语言开发的,或者我们需要通过nginx
做反向代理时需要我们如何操作呢?
我们以GO
语言为例,golang
支持pem
证书认证,而不支持我们生成的p12
,所以需要我们将p12
证书转换为pem
证书
openssl pkcs12 -in zkcsdn.p12 -out zkcsdn.crt -nokeys openssl pkcs12 -in zkcsdn.p12 -out zkcsdn.key -nocerts -nodes
在上述命令中,我们使用openssl
命令提取客户端证书和私钥。第一行提取证书并将其保存为zkcsdn.crt
文件。第二行提取私钥并将其保存为zkcsdn.key
文件。使用-nokeys
选项不要包括私钥,而使用“-nocerts -nodes”选项来指示openssl
不要包括证书链,并且不要加密私钥。
golang http
客户端集成示例
func main() { // 加载证书配置 cert, err := tls.LoadX509KeyPair("zkcsdn.crt", "zkcsdn.key") if err != nil { fmt.Println("Error loading client cert: ", err) return } // 创建TLS相关配置 tlsConfig := &tls.Config{ Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true, } // 创建http客户端 transport := &http.Transport{TLSClientConfig: tlsConfig} httpClient := &http.Client{Transport: transport} // 发送HTTPS请求 }