1 簡介
不知不覺Https
相關的文章已經寫了6篇了,本文將是這個專題的最后一篇,起碼近期是最后一篇。前面6篇講的全都是單向的Https
驗證,本文將重點介紹一下雙向驗證。有興趣的同學可以了解一下之前的文章:
(0)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里。
以Java程序為例,我們建立SSLContext時,需要生成KeyManager和TrustManager,對應的參數為javax.net.ssl.keyStore和javax.net.ssl.trustStore。而它們的作用正好體現了不同Store的作用:
-
TrustManager:決定對方來的cert是不是可信的;
-
KeyManager:決定自己發什么cert給對方。
如果我們不指定TrustStore,默認是$JAVA_HOME/jre/lib/security/cacerts
文件。我們可以通過命令查看這個文件都有什么certs:
keytool -list -keystore cacerts
2.3 單向驗證的流程
單向驗證的流程如下圖所示:
建立連接的過程:
-
客戶端向服務端發送SSL協議版本號、加密算法種類、隨機數等信息;
-
服務端給客戶端返回SSL協議版本號、加密算法種類、隨機數等信息,同時也返回服務器端的證書,即公鑰證書;
-
客戶端使用服務端返回的信息驗證服務器端的合法性,包括:
(1)證書是否過期
(2)頒發服務器證書的CA是否可靠
(3)返回的公鑰是否能正確解開返回證書中的數字簽名
(4)服務器證書上的域名是否和服務器的實際域名相匹配
驗證通過后,將繼續進行通信,否則,終止通信; -
客戶端向服務端發送自己所能支持的對稱加密方案,供服務器端進行選擇;
-
服務器端在客戶端提供的加密方案中選擇加密程度最高的加密方式;
-
服務器將選擇好的加密方案通過明文方式返回給客戶端;
-
客戶端接收到服務端返回的加密方式后,使用該加密方式生成產生隨機碼,用作通信過程中對稱加密的密鑰,使用服務端返回的公鑰進行加密,將加密后的隨機碼發送至服務器;
-
服務器收到客戶端返回的加密信息后,使用自己的私鑰進行解密,獲取對稱加密密鑰。在接下來的會話中,服務器和客戶端將會使用該密碼進行對稱加密,保證通信過程中信息的安全。
2.4 雙向驗證的流程
雙向驗證的流程如下圖所示:
建立連接的過程:
-
客戶端向服務端發送SSL協議版本號、加密算法種類、隨機數等信息;
-
服務端給客戶端返回SSL協議版本號、加密算法種類、隨機數等信息,同時也返回服務器端的證書,即公鑰證書;
-
客戶端使用服務端返回的信息驗證服務器端的合法性,包括:
(1)證書是否過期
(2)頒發服務器證書的CA是否可靠
(3)返回的公鑰是否能正確解開返回證書中的數字簽名
(4)服務器證書上的域名是否和服務器的實際域名相匹配
驗證通過后,將繼續進行通信,否則,終止通信; -
服務端要求客戶端發送客戶端的證書,客戶端會將自己的證書發送至服務端;
-
驗證客戶端的證書,通過驗證后,會獲得客戶端的公鑰;
-
客戶端向服務端發送自己所能支持的對稱加密方案,供服務器端進行選擇;
-
服務器端在客戶端提供的加密方案中選擇加密程度最高的加密方式;
-
將加密方案通過使用之前獲取到的公鑰進行加密,返回給客戶端;
-
客戶端收到服務端返回的加密方案密文后,使用自己的私鑰進行解密,獲取具體加密方式,而后,產生該加密方式的隨機碼,用作加密過程中的密鑰,使用之前從服務端證書中獲取到的公鑰進行加密后,發送給服務端;
-
服務端收到客戶端發送的消息后,使用自己的私鑰進行解密,獲取對稱加密的密鑰,在接下來的會話中,服務器和客戶端將會使用該密碼進行對稱加密,保證通信過程中信息的安全。
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 Store
和Trust Store
的文件、密碼等信息,即使是同一個文件。
需要注意的是,server.ssl.client-auth
有三個可配置的值:none
、want
和need
。雙向驗證應該配置為need
;none
表示不驗證客戶端;want
表示會驗證,但不強制驗證,即驗證失敗也可以成功建立連接。
3.3 用Postman測試雙向驗證
完成密鑰文件准備和配置后,啟動Springboot
便可以了。這里用Postman
訪問如下:
無法建立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
如下圖所示:
再重新訪問,成功了!
或者我們可以把密鑰文件拆成private key
和cert
,命令如下:
# 導出客戶端的cert文件
openssl pkcs12 -nokeys -in client.p12 -out client.pem
# 導出客戶端的key文件
openssl pkcs12 -nocerts -nodes -in client.p12 -out client.key
把客戶端的密鑰文件配置到Postman
上,如圖所示:
結果一樣是可以成功訪問:
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
An Overview of One-Way SSL and Two-Way SSL
驗證流程部分來自:Https單向認證和雙向認證
歡迎訪問南瓜慢說 www.pkslow.com獲取更多精彩文章!
歡迎關注微信公眾號<南瓜慢說>,將持續為你更新...
多讀書,多分享;多寫作,多整理。