JavaRush /Java 博客 /Random-ZH /从 HTTP 到 HTTPS
Viacheslav
第 3 级

从 HTTP 到 HTTPS

已在 Random-ZH 群组中发布
从 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