JavaRush /Java Blog /Random-TW /從 HTTP 到 HTTPS
Viacheslav
等級 3

從 HTTP 到 HTTPS

在 Random-TW 群組發布
從 HTTP 到 HTTPS - 1
內容:

介紹

在現代世界,您的生活離不開網頁應用程式。我們將從一個小實驗開始。小時候,我記得所有的攤位都賣《論點與事實》這樣的報紙。我之所以記得它們,是因為根據我個人從小的感覺,這些報紙總是看起來很奇怪。我決定我們是否應該訪問他們的網站:
從 HTTP 到 HTTPS - 2
如果我們前往 Google Chrome 協助,我們會看到網站不使用安全連接,而您與該網站交換的資訊可能會被第三方存取。我們再看看其他一些新聞,像是電子媒體Fontanka的聖彼得堡新聞:
從 HTTP 到 HTTPS - 3
正如您所看到的,根據這些數據,Fontanka 網站不存在安全問題。事實證明,網路資源可能安全,也可能不安全。我們也看到,對未受保護資源的存取是透過 HTTP 協定進行的。而如果資源受到保護,則使用HTTPS協定進行資料交換,其中末尾的S表示「安全」。HTTPS協定在rfc2818規格中描述:「HTTP Over TLS」。讓我們嘗試創建自己的 Web 應用程式並親眼看看它是如何運作的。一路上我們會理解這些術語。
從 HTTP 到 HTTPS - 4

Java 中的 Web 應用程式

因此,我們需要用 Java 建立一個非常簡單的 Web 應用程式。首先,我們需要 Java 應用程式本身。為此,我們將使用 Gradle 專案的自動建置系統。這將使我們不必手動建立必要的目錄結構+ Gradle 將為我們管理專案所需的所有庫,並確保它們在執行程式碼時可用。您可以在簡短的評論中閱讀有關 Gradle 的更多資訊:「Gradle 簡介」。讓我們使用Gradle Init Plugin並執行指令:
gradle init --type java-application
之後,讓我們打開建置腳本build.gradle,它描述了我們的專案由哪些庫組成,Gradle 將向我們提供哪些庫。讓我們新增對我們將進行實驗的 Web 伺服器的依賴項:
dependencies {
    // Web server
    implementation 'io.undertow:undertow-core:2.0.20.Final'
     // Use JUnit test framework
     testImplementation 'junit:junit:4.12'
}
為了使 Web 應用程式正常運作,我們肯定需要一個 Web 伺服器來託管我們的應用程式。Web 伺服器種類繁多,但主要的是:Tomcat、Jetty、Undertow。這次我們會選擇Undertow。要了解如何使用我們的 Web 伺服器,請造訪Undertow官方網站並前往文件部分。你和我已經連接了對 Undertow Core 的依賴,因此我們對有關這個Core 的部分感興趣,即核心,Web 伺服器的基礎。最簡單的方法是使用 Undertow 的 Builder API:
public static void main(String[] args) {
	Undertow server = Undertow.builder()
            .addHttpListener(8080, "localhost")
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
    server.start();
}
如果執行程式碼,我們可以導航到以下 Web 資源:
從 HTTP 到 HTTPS - 5
它的工作原理很簡單。借助 Undertow Builder API,我們為本機主機和連接埠 8080 新增了一個 HTTP 偵聽器。該偵聽器接收來自 Web 瀏覽器的請求並傳回字串「Hello World」作為回應。很棒的網頁應用程式。但正如我們所見,我們使用 HTTP 協議,即 這種類型的資料交換並不安全。讓我們來看看HTTPS協定是如何進行交換的。
從 HTTP 到 HTTPS - 6

HTTPS 的要求

要了解如何啟用 HTTPS,讓我們回到 HTTPS 規格:「RFC-2818:HTTP Over TLS」。根據規範,HTTPS協定中的資料透過加密協定SSL或TLS傳輸。人們經常被 SSL 和 TLS 的概念誤導。事實上,SSL 已經發展並改變了它的版本。後來,TLS 成為 SSL 協定發展的下一步。也就是說,TLS 只是 SSL 的新版本。該規格是這樣說的:「SSL 及其後繼者 TLS」。因此,我們了解到有 SSL/TLS 加密協定。 SSL是Secure Sockets Layer的縮寫,翻譯為「安全通訊端層」。Socket翻譯自英文就是連接器。透過網路進行資料傳輸的參與者使用套接字作為程式介面(即 API),透過網路相互通訊。瀏覽器充當客戶端並使用客戶端套接字,接收請求並發出回應的伺服器使用伺服器套接字。資料交換就是在這些套接字之間發生的。這就是該協定最初被稱為 SSL 的原因。但隨著時間的推移,協議也在不斷發展。SSL 協定一度成為 TLS 協定。TLS 是傳輸層安全的縮寫。TLS 協定又基於 SSL 協定規格 3.0 版。TLS 協議是單獨的文章和評論的主題,因此我將簡單地指出我認為有趣的材料: 簡而言之,HTTPS 的基礎是 TLS 握手和使用其數位憑證的「伺服器身分」檢查(即伺服器識別)。這很重要。讓我們記住這一點,因為... 我們稍後會再回到這個事實。因此,之前我們使用 HttpListener 來告訴伺服器如何透過 HTTP 協定進行操作。如果在上面的範例中我們新增了一個 HttpListener 以透過 HTTP 運作,那麼要透過 HTTPS 工作,我們需要新增一個 HttpsListener:
從 HTTP 到 HTTPS - 7
但要添加它,我們需要 SSLContext。有趣的是,SSLContext 不是 Undertow 的類,而是javax.net.ssl.SSLContext. SSLContext 類別是所謂的「Java 安全通訊端擴充功能」(JSSE) 的一部分 - 一個用於確保 Internet 連線安全的 Java 擴充。此擴充功能在「 Java 安全通訊端擴充 (JSSE) 參考指南」中進行了描述。從文件的介紹部分可以看到,JSSE 提供了 SSL 和 TLS 協定的框架和 Java 實作。我們如何取得 SSLContext?開啟 JavaDoc SSLContext 並找到getInstance方法。如您所見,要取得 SSLContext,我們需要指定名稱「安全套接字協定」。參數的描述顯示這些名稱可以在《Java密碼體系結構標準演算法名稱文件》中找到。因此,讓我們按照說明操作並查看文件。我們看到我們可以在 SSL 和 TLS 之間進行選擇:
從 HTTP 到 HTTPS - 8
現在我們知道我們需要建立 SSLContext,如下所示:
public SSLContext getSSLContext() {
	// 1. Получаем контекст, в рамках которого будем работать по TLS протоколу
	SSLContext context = null;
	try {
		context = SSLContext.getInstance("TLS");
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	}
	return context;
}
建立新上下文後,我們記得「 Java 安全套接字擴充 (JSSE) 參考指南」 中描述了 SSLContext 。我們讀到並看到「新建立的 SSLContext 應該透過呼叫 init 方法來初始化」。也就是說,創建上下文是不夠的。它需要被初始化。這是合乎邏輯的,因為 關於安全性,我們只是告訴您我們要使用 TLS 協定。為了初始化 SSLContext,我們需要提供三件事:KeyManager、TrustManager、SecureRandom。
從 HTTP 到 HTTPS - 9

密鑰管理器

KeyManager是金鑰管理器。他負責向與我們聯繫的人提供什麼「身份驗證憑證」。憑證可以翻譯為身分。需要身份,以便客戶端確信伺服器就是他所聲稱的身份並且可以信任。什麼將被用作身份識別?我們記得,伺服器身份是透過伺服器的數位憑證來驗證的。這個過程可以表示如下:
從 HTTP 到 HTTPS - 10
另外,《JSSE參考指南:SSL如何運作》中說SSL使用“非對稱加密技術”,這意味著我們需要一個金鑰對:公鑰和私鑰。既然我們正在談論密碼學,「Java 密碼學架構」(JCA)就開始發揮作用。Oracle 提供了關於此架構的優秀文件:「Java 加密架構 (JCA) 參考指南」。另外,你可以在JavaRush上閱讀JCA的簡要概述:《Java密碼學架構:初識》。因此,為了初始化 KeyManager,我們需要一個 KeyStore,它將儲存我們伺服器的憑證。建立金鑰和憑證儲存的最常見方法是 keytool 實用程序,它包含在 JDK 中。JSSE 文件中可以看到一個範例:「Creating a Keystore to Use with JSSE」。因此,我們需要使用 KeyTool 實用程式建立密鑰儲存並在其中寫入憑證。有趣的是,之前使用 -genkey 指定金鑰生成,但現在建議使用 -genkeypair。我們需要定義以下內容:
  • alias : 別名或只是將條目保存在金鑰庫中的名稱
  • keyalg:金鑰加密演算法。讓我們選擇 RSA 演算法,它本質上是我們目的的標準解決方案。
  • keysize:密鑰大小(以位元為單位)。建議的最小大小是 2048,因為... 較小的尺寸已經被破解。您可以在這裡閱讀更多內容:「2048 位元 ssl 憑證」。
  • dname:專有名稱,專有名稱。
重要的是要了解所要求的資源(例如,https://localhost)將與其進行比較。這稱為“主題 cn 匹配”。
  • 有效性:產生的證書有效的持續時間(以天為單位),即 有效的。
  • ext :「命名擴充功能」中指定的憑證擴充。
對於自簽名憑證(即獨立建立的憑證),您必須指定以下副檔名:
  • -ext san:ritic=dns:localhost,ip:127.0.0.1 > 按SubjectAlternativeName 執行主題匹配
  • -ext bc=ca:false > 表示該證書不用於簽署其他證書
讓我們執行命令(以 Windows 作業系統為例):
keytool -genkeypair -alias ssl -keyalg RSA -keysize 2048 -dname "CN=localhost,OU=IT,O=Javarush,L=SaintPetersburg,C=RU,email=contact@email.com" -validity 90 -keystore C:/keystore.jks -storepass passw0rd -keypass passw0rd -ext san:critical=dns:localhost,ip:127.0.0.1 -ext bc=ca:false
因為 將建立該文件,請確保您擁有創建該文件的所有權限。您也可能會看到這樣的建議:
從 HTTP 到 HTTPS - 11
這裡我們被告知 JKS 是一種專有格式。專有意味著它是作者的私有財產,並且僅供在 Java 中使用。與第三方實用程式合作時,可能會出現衝突,這就是我們收到警告的原因。此外,我們可能會收到錯誤:The destination pkcs12 keystore has different storepass and keypass。發生此錯誤的原因是密鑰庫中條目的密碼與密鑰庫本身的密碼不同。正如keytool文件所述,“例如,大多數第三方工具要求 PKCS #12 金鑰庫中的 storepass 和 keypass 相同。” 我們可以自己指定金鑰(例如,-destkeypass entrypassw)。但最好不要違反要求,設定相同的密碼。因此導入可能如下所示:
keytool -importkeystore -srckeystore C:/keystore.jks -destkeystore C:/keystore.jks -deststoretype pkcs12
成功範例:
從 HTTP 到 HTTPS - 12
要將證書匯出到文件,您可以運行:
keytool -export -alias ssl -storepass passw0rd -file C:/server.cer -keystore C:/keystore.jks
此外,我們還可以取得 Keystore 內容,如下所示:
keytool -list -v -keystore C:/keystore.jks -storepass passw0rd
太好了,現在我們有了一個包含憑證的金鑰庫。現在你可以從程式碼中取得它:
public KeyStore getKeyStore() {
	// Согласно https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#KeyStore
	try(FileInputStream fis = new FileInputStream("C:/keystore.jks")){
		KeyStore keyStore = KeyStore.getInstance("pkcs12");
		keyStore.load(fis, "passw0rd".toCharArray());
		return keyStore;
	} catch (IOException ioe) {
		throw new IllegalStateException(ioe);
	} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
		throw new IllegalStateException(e);
	}
}
如果有KeyStore,那我們可以初始化KeyManager:
public KeyManager[] getKeyManagers(KeyStore keyStore) {
	String keyManagerAlgo = KeyManagerFactory.getDefaultAlgorithm();
	KeyManagerFactory keyManagerFactory = null;
	try {
		keyManagerFactory = KeyManagerFactory.getInstance(keyManagerAlgo);
		keyManagerFactory.init(keyStore, "passw0rd".toCharArray());
		return keyManagerFactory.getKeyManagers();
	} catch (NoSuchAlgorithmException e) {
		throw new IllegalStateException(e);
	} catch (UnrecoverableKeyException | KeyStoreException e) {
		throw new IllegalStateException(e);
	}
}
我們的第一個目標已經實現了。仍有待弄清楚 TrustManager 是什麼。TrustManager 在 JSSE 文件的「 TrustManager 介面」部分中進行了說明。它與 KeyManager 非常相似,但其目的是檢查請求連接的人是否可信。說穿了,這就是KeyManager的逆向 =) 我們不需要TrustManager,所以就傳null。然後將建立一個預設的 TrustManager,它不會驗證向我們的伺服器發出請求的最終使用者。該文檔是這樣說的:「將使用預設實作」。與 SecureRandom 相同。如果我們指定 null,則將使用預設實作。讓我們記住,SecureRandom 是一個 JCA 類,並且在 JCA 文件的「SecureRandom 類」部分中進行了描述。總的來說,考慮到上述所有方法的準備工作可能如下所示:
public static void main(String[] args) {
	// 1. Подготавливаем приложение к работе по HTTPS
	App app = new App();
	SSLContext sslContext = app.getSSLContext();
	KeyStore keyStore = app.getKeyStore();
	KeyManager[] keyManagers = app.getKeyManagers(keyStore);
	try {
		sslContext.init(keyManagers, null, null);
	} catch (KeyManagementException e) {
		throw new IllegalStateException(e);
	}
剩下的就是啟動伺服器:
// 2. Поднимаем server
 	int httpsPort = 443;
	Undertow server = Undertow.builder()
            .addHttpsListener(httpsPort, "localhost", sslContext)
            .setHandler(new HttpHandler() {
                @Override
                public void handleRequest(final HttpServerExchange exchange) throws Exception {
                    exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/plain");
                    exchange.getResponseSender().send("Hello World");
                }
            }).build();
	server.start();
}
這次我們的伺服器將在以下位址可用https://localhost:443 但是,我們仍然會收到一條錯誤訊息,指出該資源不可信:
從 HTTP 到 HTTPS - 13
讓我們弄清楚證書出了什麼問題以及如何處理。
從 HTTP 到 HTTPS - 14

證書管理

因此,我們的伺服器已經準備好透過 HTTPS 工作,但客戶端不信任它。為什麼?我們來看一下:
從 HTTP 到 HTTPS - 15
原因是該憑證是自簽名憑證。自簽名 SSL 憑證是指由其標識的同一個人所頒發和簽署的公鑰憑證。也就是說,它不是由任何受人尊敬的證書頒發機構(CA,也稱為證書頒發機構)頒發的。證書頒發機構扮演受託人的角色,類似於日常生活中的公證人。他保證他頒發的證書是可靠的。此類 CA 頒發證書的服務是有償的,因此沒有人需要承擔失去信任和聲譽的風險。預設情況下,有多個受信任的憑證授權單位。該列表是可編輯的。並且每個作業系統都有自己管理的認證機構清單。例如,在 Windows 中管理此清單可以在此處閱讀:「在 Windows 中管理受信任的根憑證」。讓我們將憑證新增至錯誤訊息中所示的受信任憑證。為此,首先下載證書:
從 HTTP 到 HTTPS - 16
在Windows作業系統中,按Win+R並執行,mmc呼叫控制台。接下來,按 Ctrl+M 將「憑證」部分新增至目前控制台。接下來,在「受信任的根憑證授權單位」小節中,我們將執行Действия / Все задачи / Импорт. 讓我們將先前下載的檔案匯入到該檔案中。瀏覽器可能已經記住了憑證過去的信任狀態。因此,在開啟頁面之前需要重新啟動瀏覽器。例如,在 Google Chrome 中,您需要在網址列中執行chrome://restart. 在 Windows 作業系統中,您也可以使用該實用程式檢視憑證certmgr.msc
從 HTTP 到 HTTPS - 17
如果一切正確,我們將看到透過 HTTPS 成功呼叫我們的伺服器:
從 HTTP 到 HTTPS - 18
如您所見,證書現在被視為有效,資源可用,並且沒有錯誤。
從 HTTP 到 HTTPS - 19

底線

因此,我們已經弄清楚在 Web 伺服器上啟用 HTTPS 協定的方案是什麼樣的以及為此需要什麼。我希望此時可以清楚地看到,負責加密的 Java 加密體系結構 (JCA) 和在 Java 端提供 TLS 實現的 Java 安全通訊端擴充 (JSSE) 之間的交互提供了支援。我們了解如何使用 JDK 中包含的 keytool 實用程式來處理 KeyStore 金鑰和憑證儲存。此外,我們意識到 HTTPS 使用 SSL/TLS 協定來確保安全。為了強化這一點,我建議您閱讀有關該主題的優秀文章: 希望經過這次小小的審查,HTTPS 將變得更加透明一些。如果您需要啟用 HTTPS,您可以從應用程式伺服器和框架的文檔中輕鬆理解這些術語。#維亞切斯拉夫
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION