Https雙向驗證與Springboot整合測試-人來人往我只認你

1 簡介

不知不覺Https相關的文章已經寫了6篇了,本文將是這個專題的最後一篇,起碼近期是最後一篇。前面6篇講的全都是單向的Https驗證,本文將重點介紹一下雙向驗證。有興趣的同學可以瞭解一下之前的文章:

(0)Https專題

(1)Springboot整合https原來這麼簡單

(2)HTTPS之密鑰知識與密鑰工具Keytool和Keystore-Explorer

(3)Springboot以Tomcat爲容器實現http重定向到https的兩種方式

(4) Springboot以Jetty爲容器實現http重定向到https

(5)nginx開啓ssl並把http重定向到https的兩種方式

(6)Springboot-WebFlux實現http重定向到https

雙向驗證是比較難的,能掌握雙向驗證,單向驗證就沒什麼問題了。

2 單向驗證與雙向驗證

2.1 概念和作用

所謂單向驗證One Way SSL),就是隻有一方驗證另一方是否合法,通常是客戶端驗證服務端。比如我們打開www.pkslow.com 這個網站,服務端不會驗證客戶端,只要你來我就歡迎。但客戶端不一樣,瀏覽器會驗證這個網站是不是安全的,一般是通過CA頒發的SSL證書來驗證。

單向驗證

雙向驗證Two Way SSL)則不同。不僅客戶端需要驗證服務端,服務端同樣戒備心很重,也需要驗證客戶端是否是合法。

雙向驗證

大家是否會有疑問,這麼麻煩的雙向驗證有什麼用?我們平常用單向驗證不就已經足夠了嗎?單向驗證雖然安全,但不夠安全,使用雙向驗證可以只讓特定的客戶端訪問,安全性會高一點。

另一方面,如果服務端沒有做賬戶權限控制,但又想只限制特定的客戶端訪問,雙向驗證就非常有用,我只驗證我相信的客戶端,其它一概拒絕!這樣即使我的服務暴露在網上,也不怕別人訪問。既保證了安全,又省去了做權限控制的麻煩。

2.2 驗證合法性

驗證合法性通常是通過Trust Store。要求要把對方的cert裝在自己的Trust Store裏。

這就衍生出了一個之前沒有講過的概念:Trust Store。密鑰文件可以存放私鑰和公鑰,具體一點來說是可以存放自己的私鑰、自己的公鑰和別人的公鑰(也可以存放別人的私鑰,但這不合理,私鑰必須私有)。一般地我們把自己的(私鑰和公鑰)存放在Key Store裏,而把別人的公鑰存放在Trust Store裏。

Trust-Store

Java程序爲例,我們建立SSLContext時,需要生成KeyManagerTrustManager,對應的參數爲javax.net.ssl.keyStorejavax.net.ssl.trustStore。而它們的作用正好體現了不同Store的作用:

  • TrustManager:決定對方來的cert是不是可信的;

  • KeyManager:決定自己發什麼cert給對方。

如果我們不指定TrustStore,默認是$JAVA_HOME/jre/lib/security/cacerts文件。我們可以通過命令查看這個文件都有什麼certs

keytool -list -keystore cacerts

2.3 單向驗證的流程

單向驗證的流程如下圖所示:

單向驗證的流程

建立連接的過程:

  1. 客戶端向服務端發送SSL協議版本號、加密算法種類、隨機數等信息;

  2. 服務端給客戶端返回SSL協議版本號、加密算法種類、隨機數等信息,同時也返回服務器端的證書,即公鑰證書;

  3. 客戶端使用服務端返回的信息驗證服務器端的合法性,包括:

    (1)證書是否過期 (2)頒發服務器證書的CA是否可靠 (3)返回的公鑰是否能正確解開返回證書中的數字簽名 (4)服務器證書上的域名是否和服務器的實際域名相匹配 驗證通過後,將繼續進行通信,否則,終止通信;

  4. 客戶端向服務端發送自己所能支持的對稱加密方案,供服務器端進行選擇;

  5. 服務器端在客戶端提供的加密方案中選擇加密程度最高的加密方式

  6. 服務器將選擇好的加密方案通過明文方式返回給客戶端;

  7. 客戶端接收到服務端返回的加密方式後,使用該加密方式生成產生隨機碼,用作通信過程中對稱加密的密鑰,使用服務端返回的公鑰進行加密,將加密後的隨機碼發送至服務器;

  8. 服務器收到客戶端返回的加密信息後,使用自己的私鑰進行解密,獲取對稱加密密鑰。在接下來的會話中,服務器和客戶端將會使用該密碼進行對稱加密,保證通信過程中信息的安全。

2.4 雙向驗證的流程

雙向驗證的流程如下圖所示:

雙向驗證的流程

建立連接的過程:

  1. 客戶端向服務端發送SSL協議版本號、加密算法種類、隨機數等信息;

  2. 服務端給客戶端返回SSL協議版本號、加密算法種類、隨機數等信息,同時也返回服務器端的證書,即公鑰證書;

  3. 客戶端使用服務端返回的信息驗證服務器端的合法性,包括:

    (1)證書是否過期 (2)頒發服務器證書的CA是否可靠 (3)返回的公鑰是否能正確解開返回證書中的數字簽名 (4)服務器證書上的域名是否和服務器的實際域名相匹配 驗證通過後,將繼續進行通信,否則,終止通信;

  1. 服務端要求客戶端發送客戶端的證書,客戶端會將自己的證書發送至服務端

  2. 驗證客戶端的證書,通過驗證後,會獲得客戶端的公鑰

  3. 客戶端向服務端發送自己所能支持的對稱加密方案,供服務器端進行選擇;

  4. 服務器端在客戶端提供的加密方案中選擇加密程度最高的加密方式

  5. 將加密方案通過使用之前獲取到的公鑰進行加密,返回給客戶端;

  6. 客戶端收到服務端返回的加密方案密文後,使用自己的私鑰進行解密,獲取具體加密方式,而後,產生該加密方式的隨機碼,用作加密過程中的密鑰,使用之前從服務端證書中獲取到的公鑰進行加密後,發送給服務端;

  7. 服務端收到客戶端發送的消息後,使用自己的私鑰進行解密,獲取對稱加密的密鑰,在接下來的會話中,服務器和客戶端將會使用該密碼進行對稱加密,保證通信過程中信息的安全。

3 Springboot整合雙向驗證

理論知識講完了,就來實戰一下,Springboot是怎麼整合雙向驗證的。

3.1 生成密鑰文件

既然是雙向驗證,就需要雙方的密鑰,我們服務端稱爲localhost,而客戶端稱爲client。需要生成雙方的密鑰文件,並把對方的cert導入自己的密鑰文件裏。整個過程如下:

注意:密碼統一爲changeit

# 生成服務端密鑰文件localhost.jks
keytool -genkey -alias localhost -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore localhost.jks -dname CN=localhost,OU=Test,O=pkslow,L=Guangzhou,C=CN -validity 731 -storepass changeit -keypass changeit

# 導出服務端的cert文件
keytool -export -alias localhost -file localhost.cer -keystore localhost.jks

# 生成客戶端的密鑰文件client.jks
keytool -genkey -alias client -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -keystore client.jks -dname CN=client,OU=Test,O=pkslow,L=Guangzhou,C=CN -validity 731 -storepass changeit -keypass changeit

# 導出客戶端的cert文件
keytool -export -alias client -file client.cer -keystore client.jks

# 把客戶端的cert導入到服務端
keytool -import -alias client -file client.cer -keystore localhost.jks

# 把服務端的cert導入到客戶端
keytool -import -alias localhost -file localhost.cer -keystore client.jks

# 檢驗服務端是否具有自己的private key和客戶端的cert
keytool -list -keystore localhost.jks

成功執行完上述步驟後,會生成4個文件:

  • 服務端密鑰文件: localhost.jks

  • 服務端cert文件:localhost.cer

  • 客戶端密鑰文件:client.jks

  • 客戶端cert文件:client.cer

實際上cert文件可以不要了,因爲已經導入對方的Trust Store裏面去了。也就是在文件localhost.jks裏,包含了服務端的私鑰、公鑰還有客戶端的公鑰。client.jks同理。

3.2 配置Spirngboot

Springboot的配置文件如下:

server.port=443

server.ssl.enabled=true
server.ssl.key-store-type=JKS
server.ssl.key-store=classpath:localhost.jks
server.ssl.key-store-password=changeit
server.ssl.key-alias=localhost

server.ssl.trust-store=classpath:localhost.jks
server.ssl.trust-store-password=changeit
server.ssl.trust-store-provider=SUN
server.ssl.trust-store-type=JKS
server.ssl.client-auth=need

需要分別配置Key StoreTrust Store的文件、密碼等信息,即使是同一個文件。

需要注意的是,server.ssl.client-auth有三個可配置的值:nonewantneed。雙向驗證應該配置爲neednone表示不驗證客戶端;want表示會驗證,但不強制驗證,即驗證失敗也可以成功建立連接。

3.3 用Postman測試雙向驗證

完成密鑰文件準備和配置後,啓動Springboot便可以了。這裏用Postman訪問如下:

Postman Failed

無法建立https連接,無法訪問。原因就是Postman作爲客戶端並沒有合法的cert。

爲了建立連接,應該要把客戶端的密鑰文件給Postman使用。因爲JKS是Java的密鑰文件格式,我們轉換成通用的PKCS12格式如下:

# 轉換JKS格式爲P12
keytool -importkeystore -srckeystore client.jks -destkeystore client.p12 -srcstoretype JKS -deststoretype PKCS12 -srcstorepass changeit -deststorepass changeit -srckeypass changeit -destkeypass changeit -srcalias client -destalias client -noprompt

把客戶端密鑰文件配置到Postman如下圖所示:

Postman Config PFX

再重新訪問,成功了!

Postman Success

或者我們可以把密鑰文件拆成private keycert,命令如下:

# 導出客戶端的cert文件
openssl pkcs12 -nokeys -in client.p12 -out client.pem

# 導出客戶端的key文件
openssl pkcs12 -nocerts -nodes -in client.p12 -out client.key

把客戶端的密鑰文件配置到Postman上,如圖所示:

Postman Config key

結果一樣是可以成功訪問:

Postman Success

3.4 用curl命令測試

沒有安裝Postman怎麼辦呢?還好,用強大的curl命令也是可以測試的。命令如下:

curl -k --cert client.pem --key client.key https://localhost/hello
# 下面命令可以查看建立SSL連接詳情
curl -v -k --cert client.pem --key client.key https://localhost/hello

如果覺得指定兩個文件太麻煩,可以只生成一個文件,命令如下:

openssl pkcs12 -nodes -in client.p12 -out client_all.pem

則連接命令變成了:

# 需要指定密碼
curl -k --cert client_all.pem:changeit https://localhost/hello

4 總結

這篇文章講解了單向驗證和雙向驗證的區別及流程,並用實例展示如何實現雙向驗證,相信跟着做一遍,基本都能理解了。

參考資料:

Trust Store vs Key Store - creating with keytool

Keystore vs. Truststore

An Overview of One-Way SSL and Two-Way SSL

驗證流程部分來自:Https單向認證和雙向認證


歡迎訪問南瓜慢說 www.pkslow.com獲取更多精彩文章!

歡迎關注微信公衆號<南瓜慢說>,將持續爲你更新...

多讀書,多分享;多寫作,多整理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章