Skip to content

keytool 生成证书之 SpringBoot 双向认证

(规范的做法)配置 SpringBoot

yaml
server:
  ssl:
    enabled: true
    # 密钥库,单向认证。也可以配置绝对路径:key-store: C:\Users\mengweijin\keystore.jks
    key-store: classpath:keystore.jks
    key-store-password: 123456
    key-store-type: JKS
    key-alias: mengweijin
    # 客户端证书认证,双向认证。可选值为:none、want 和 need
    client-auth: need
    # 信任库,这里需要配置 truststore
    trust-store: classpath:truststore.jks
    trust-store-password: 123456
    trust-store-type: JKS
    trust-store-provider: SUN

需要分别配置 Key Store 和 Trust Store 的文件、密码等信息,即使是同一个文件。

需要注意的是,server.ssl.client-auth 有三个可配置的值:none、want 和 need。双向验证应该配置为 need;none 表示不验证客户端;want 表示会验证,但不强制验证,即验证失败也可以成功建立连接。

注意:如果启动 SpringBoot 时报错 java.io.IOException:Invalid keystore format,请检查 maven 中是否有如下配置,改为 false 即可。但一般情况下,证书文件都是放在服务器上面的,不会打在 jar 包中,因此,不会有这个影响。这个错误多见与开发环境。

如果改为 false 会影响其他功能,那么就不建议把 jks 文件和受影响的功能的文件全部放到相同的 src/main/resources 目录下,建议分开文件夹放,并修改 maven 中的配置。

xml
<build>
    <resources>
        <resource>
            <!-- 防止 JKS 被 maven 错误解析 -->
            <directory>src/main/resources</directory>
            <filtering>false</filtering>
        </resource>
    </resources>
</build>

(规范的做法)分别创建服务端(服务 A)和客户端(服务 B)的 keystore、 truststore

从上面 SpringBoot 的配置中可以得知,一个应用如果启用双向认证,理论上需要一个 keystore 和一个 truststore(不规范的做法是只使用相同的一个密钥库)。那么假如两个服务 A 和 B 都需要做双向认证,就需要分别创建 服务 A 和 服务 B 的 keystore 和 truststore(共计四个密钥库,只是 truststore 用来导入保存自己应用信任的客户端证书,可以不需要设置密码)。

由于个人测试用,为了方便起见,以下我们的 keystore 和 truststore 都使用同一个密钥库。因此,我们只需要给服务 A 创建一个密钥库(mengweijin.jks)作为服务端,给服务 B 创建一个密钥库(client.jks)作为客户端,然后把对方的证书导入到自己的密钥库就是添加了信任。

为了测试方便,我们只对服务端的服务 A(使用 mengweijin.jks)开启双向认证,也就是说,要把客户端 client.jks 导出的证书 client.cer 导入到服务端的 truststore(这里也作为 keystore,两个密钥库合一了而已。)mengweijin.jks 中,服务 A 就信任了来自服务 B 的证书。

此时,B 服务在调用 A 服务时,只需要带上 B 服务自己由 client.jks 导出的 client.crt 和 client.key 文件,就可以通过服务 A 的客户端认证。

正式开始

整个过程如下。注意:密码统一为 123456

bash
# 创建服务端密钥库 mengweijin.jks
keytool -genkeypair -alias mengweijin -validity 36500 -keyalg RSA -dname "CN=mengweijin.com,OU=com.mengweijin,O=mengweijin,L=xi an,ST=shan xi, C=CN" -keypass 123456 -storepass 123456 -storetype JKS -keystore mengweijin.jks

# 导出服务端证书文件
keytool -v -export -alias mengweijin -keystore mengweijin.jks -storepass 123456 -rfc -file mengweijin.crt


# 生成客户端的密钥库 client.jks
keytool -genkeypair -alias client -validity 36500 -keyalg RSA -dname "CN=client.com,OU=com.client,O=client,L=xi an,ST=shan xi, C=CN" -keypass 123456 -storepass 123456  -storetype JKS -keystore client.jks

# 导出客户端证书文件
keytool -v -export -alias client -keystore client.jks -storepass 123456 -rfc -file client.crt

# 把客户端证书(client.crt中要处理的条目的别名为 client)导入到服务端 truststore 中(是否信任此证书? [否]:  Y)
keytool -import -file client.crt -alias client -keystore mengweijin.jks -storepass 123456

# 如果要对客户端也做双向认证,那就把服务端证书(mengweijin.crt中要处理的条目的别名为 mengweijin)导入到客户端 truststore 中(是否信任此证书? [否]:  Y)
# 这里测试只针对服务端,客户端不需要开启双向认证,因此跳过。
#keytool -import -file mengweijin.crt -alias mengweijin -keystore client.jks -storepass 123456

# 检验服务端是否具有自己的private key和客户端的cert
keytool -list -keystore mengweijin.jks -storepass 123456

# 示例:
C:\Source\code\gitee\quickboot\sample-quickboot-mybatis-plus\src\main\resources>keytool -list -keystore mengweijin.jks -storepass 123456
密钥库类型: jks
密钥库提供方: SUN
您的密钥库包含 2 个条目
# 可以在这里看到,条目为 client 证书指纹标记为:trustedCertEntry
client, 2021-4-24, trustedCertEntry,
证书指纹 (SHA1): D9:4D:8D:84:7E:70:66:DB:CD:00:00:38:43:F5:59:26:5B:00:9F:E9
# 而在这里看到,条目为 mengweijin 证书指纹标记为:PrivateKeyEntry
mengweijin, 2021-4-24, PrivateKeyEntry,
证书指纹 (SHA1): 13:45:EB:40:01:EC:E4:7B:E0:57:79:DD:1B:DD:12:7A:39:0B:9F:52

成功执行完上述步骤后,会生成 4 个文件:

  • 服务端密钥文件:mengweijin.jks
  • 服务端 cert 文件:mengweijin.crt
  • 客户端密钥文件:client.jks
  • 客户端 cert 文件:client.crt

实际上 crt 文件可以不要了,因为已经导入对方的 Trust Store 里面去了。也就是在文件 mengweijin.jks 里,包含了服务端的私钥、公钥还有客户端的公钥。

由于客户端需要携带证书(client.crt 和 client.key)访问 url, 因此这里再导出客户端的 client.p12,然后导出 client.key。

bash
# client.p12
keytool -v -importkeystore -srcstoretype JKS -srckeystore client.jks -srcstorepass 123456 -srcalias client -srckeypass 123456 -deststoretype PKCS12 -destkeystore client.p12 -deststorepass 123456 -destalias client -destkeypass 123456

# client.key
openssl pkcs12 -in client.p12 -nocerts -nodes -out client.key -password pass:123456

配置 SpringBoot

yaml
server:
  ssl:
    enabled: true
    # 密钥库,单向认证。也可以配置绝对路径:key-store: C:\Users\mengweijin\mengweijin.jks
    key-store: classpath:mengweijin.jks
    key-store-password: 123456
    key-store-type: JKS
    key-alias: mengweijin
    # 客户端证书认证,双向认证。可选值为:none、want 和 need
    client-auth: need
    # 信任库,这里需要配置 truststore
    trust-store: classpath:mengweijin.jks
    trust-store-password: 123456
    trust-store-type: JKS
    trust-store-provider: SUN

用 Postman 测试双向验证

访问 URL: https://localhost:8080/user/get

返回 Could not get response

无法建立 https 连接,无法访问。原因就是 Postman 作为客户端并没有合法的证书。

为了建立连接,应该要把客户端的密钥文件给 Postman 使用。因为 JKS 是 Java 的密钥文件格式,我们需要使用通用的 PKCS12 格式。 把 p12 证书导入 postman: 点击右上角的齿轮按钮 -> settings -> 弹出设置对话框

  • General -> 关闭 SSL certificate verification 选项
  • Certificates -> Add Certificate

保存后,再次用 postman 访问 URL:https://localhost:8080/user/get

如果 postman 依然报错:Error: self signed certificate,请检查当前 URL 设置请求参数和 Request Header 的那个界面,找到 settings 选项卡,关闭 Enable SSL certificate verification 选项即可。

再次访问接口,就成功了。

或者我们可以把密钥文件拆成 private key(client.key)和 cert(client.crt),命令如下:

把客户端的密钥文件配置到 Postman 上,再次访问 URL。

用 curl 命令测试(windows 下不成功)

没有安装 Postman 怎么办呢?还好,用强大的 curl 命令也是可以测试的。

bash
curl -k --cert client.crt --key client.key https://localhost:8080/user/get

# 下面命令可以查看建立SSL连接详情
curl -v -k --cert client.crt --key client.key https://localhost:8080/user/get


# 如果觉得指定两个文件太麻烦,可以只生成一个文件,命令如下:
openssl pkcs12 -nodes -in client.p12 -out client_all.pem

# 则连接命令变成了如下(需要指定密码):
curl -k --cert client_all.pem:123456 https://localhost:8080/user/get

用浏览器测试(windows 下不成功,即使配置了 hosts,使用域名访问)

访问 URL: https://localhost:8080/user/get

然后我将生成的证书导入到浏览器中,重启浏览器继续访问测试接口,就会提示我们选择证书,点击我们导入的证书就可以正常访问了。

如何在浏览器中导入证书呢?

打开谷歌浏览器,找到设置->安全->证书管理->导入,按步骤提示导入就可以了,需要输入密码就是我们前面设置的 123456, 当然你设置了其他的密码,正常输入就可以了。然后重启浏览器再次访问就会提示我们选择证书,选择我们导入的证书,点击确定就可以了。