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)服務器證書上的域名是否和服務器的實際域名相匹配
    驗證通過后,將繼續進行通信,否則,終止通信;

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

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

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

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

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

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

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

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單向認證和雙向認證


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM