使用 Azure 密钥库 证书在 Spring Boot 中启用 HTTPS

本教程介绍如何使用 Azure 密钥库和 Azure 资源的托管标识通过 TLS/SSL 证书保护 Spring Boot(包括 Azure Spring Apps)应用。

无论是在云中还是在本地,生产级 Spring Boot 应用程序都需要使用标准 TLS 协议对网络流量进行端到端加密。 可以在公共根证书颁发机构 (CA) 中发现大多数常见 TLS/SSL 证书。 但有时这种发现无法实现。 当证书不可发现时,应用必须具备某种方法来加载此类证书、将其提供给入站网络连接以及接受来自出站网络连接的证书。

Spring Boot 应用通常通过安装证书来启用 TLS。 证书将安装到运行 Spring Boot 应用的 JVM 的本地密钥存储中。 使用 Azure 上的 Spring 时,不会在本地安装证书。 在这种情况下,Microsoft Azure 的 Spring 集成提供了一种安全且流程的方式,可以借助 Azure Key Vault 和 Azure 资源的托管标识来启用 TLS。

Diagram showing interaction of elements in this tutorial.

重要

目前,Spring Cloud Azure 证书初学者版本 4.x 或更高版本不支持 TLS/mTLS,它们仅自动配置密钥库证书客户端。 因此,如果要使用 TLS/mTLS,则无法迁移到版本 4.x。

先决条件

  • Azure 订阅 - 免费创建订阅

  • 受支持的 Java 开发工具包 (JDK) 版本 11。

  • Apache Maven 3.0 或更高版本。

  • Azure CLI

  • 用来测试功能的 cURL 或类似的 HTTP 实用工具。

  • Azure 虚拟机(VM)实例。 如果没有,请使用 az vm create 命令和 UbuntuServer 提供的 Ubuntu 映像创建启用了系统分配的托管标识的 VM 实例。 将 Contributor 角色授予系统分配的托管标识,然后设置对订阅的访问权限 scope

  • Azure Key Vault 实例。 如果没有密钥保管库,请参阅快速入门:使用Azure 门户创建密钥保管库。

  • Spring Boot 应用程序。 如果没有,请使用 Spring Initializr 创建一个 Maven 项目。 请务必选择 Maven 项目,并在“依赖项添加 Spring Web 依赖项,然后选择 Java 版本 8 或更高版本。

重要

完成本文中的步骤需要 Spring Boot 2.5 或更高版本。

设置自签名 TLS/SSL 证书

本教程中的步骤适用于任何直接存储在 Azure Key Vault 中的 TLS/SSL 证书(包括自签名的)。 自签名证书不适合用于生产,但对于开发和测试应用程序很有用。

本教程使用自签名证书。 若要设置证书,请参阅快速入门:使用Azure 门户从 Azure 密钥库设置和检索证书。

注意

设置证书后,按照分配密钥库访问策略中的说明授予 VM 对密钥库的访问权限。

通过 TLS/SSL 证书保护连接

现在已有一个 VM 和一个密钥库实例,并已授予 VM 对密钥库的访问权限。 以下部分演示如何在 Spring Boot 应用程序中通过 Azure 密钥库的 TLS/SSL 证书安全地连接。 本教程演示了以下两种方案:

  • 使用安全的入站连接来运行 Spring Boot 应用程序
  • 使用安全的出站连接来运行 Spring Boot 应用程序

提示

在以下步骤中,代码将打包到可执行文件中,并上传到 VM。 不要忘记在 VM 中安装 OpenJDK

使用安全的入站连接来运行 Spring Boot 应用程序

当入站连接的 TLS/SSL 证书来自 Azure 密钥库时,请按照以下步骤配置应用程序:

  1. 将以下依赖项添加到 pom.xml 文件:

    <dependency>
       <groupId>com.azure.spring</groupId>
       <artifactId>azure-spring-boot-starter-keyvault-certificates</artifactId>
       <version>3.14.0</version>
    </dependency>
    
  2. application.properties 配置文件中配置密钥库凭据。

    server.ssl.key-alias=<the name of the certificate in Azure Key Vault to use>
    server.ssl.key-store-type=AzureKeyVault
    server.ssl.trust-store-type=AzureKeyVault
    server.port=8443
    azure.keyvault.uri=<the URI of the Azure Key Vault to use>
    

    如本教程开头所述,这些值使 Spring Boot 应用能够对 TLS/SSL 证书执行加载操作。 下表对这些属性值进行了说明。

    properties 说明
    server.ssl.key-alias 传递给 az keyvault certificate create--name 参数的值。
    server.ssl.key-store-type 必须是 AzureKeyVault
    server.ssl.trust-store-type 必须是 AzureKeyVault
    server.port 要用于侦听 HTTPS 连接的本地 TCP 端口。
    azure.keyvault.uri az keyvault create 返回的 JSON 中的 vaultUri 属性。 已将此值保存在环境变量中。

    特定于 Key Vault 的唯一属性是 azure.keyvault.uri。 运行该应用的 VM 的系统分配的托管标识已被授予 Key Vault 的访问权限。 因此,该应用也获得了该访问权限。

    这些更改使 Spring Boot 应用能够加载 TLS/SSL 证书。 在下一步中,你将使应用能够执行 TLS/SSL 证书的接受操作,如本教程开头提及。

  3. 编辑启动类文件,使其包含以下内容。

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    @RestController
    public class SsltestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SsltestApplication.class, args);
        }
    
        @GetMapping(value = "/ssl-test")
        public String inbound(){
            return "Inbound TLS is working!!";
        }
    
        @GetMapping(value = "/exit")
        public void exit() {
            System.exit(0);
        }
    
    }
    

    从未经身份验证的 REST GET 调用中调用 System.exit(0) 仅用于演示目的。 请勿在实际应用程序中使用 System.exit(0)

    此代码演示了本教程开头提到的“呈现”操作。 下面的列表侧重说明了有关此代码的一些详细信息:

    • 现在由 Spring initializer 生成的 SsltestApplication 类上有一个 @RestController 注释。
    • 有一个用 进行@GetMappingvalue的 HTTP 调用注释的方法。
    • inbound 方法在浏览器向 /ssl-test 路径发出 HTTPS 请求时,只返回一个问候语。 inbound 方法说明服务器如何向浏览器呈现 TLS/SSL 证书。
    • 该方法 exit 会导致 JVM 在调用时退出。 此方法可便于使示例在本教程上下文中易于运行。
  4. 运行以下命令以编译代码并将其打包到可执行的 JAR 文件中。

    mvn clean package
    
  5. 验证 <your-resource-group-name> 中创建的网络安全组是否允许来自你的 IP 地址的入站流量通过端口 22 和 8443。 若要了解如何配置网络安全组规则以允许入站流量,请参阅创建、更改或删除网络安全组中的使用安全规则一节。

  6. 将可执行 JAR 文件放在 VM 上。

    cd target
    sftp azureuser@<your VM public IP address>
    put *.jar
    

    现在,你已生成 Spring Boot 应用并将其上传到 VM,请使用以下步骤在 VM 上运行它,并使用 调用 REST 终结点 curl

  7. 使用 SSH 连接到 VM,然后运行可执行的 JAR。

    set -o noglob
    ssh azureuser@<your VM public IP address> "java -jar *.jar"
    
  8. 打开新的 Bash shell,然后执行以下命令,验证服务器是否显示了 TLS/SSL 证书。

    curl --insecure https://<your VM public IP address>:8443/ssl-test
    
  9. 调用 exit 路径,终止服务器并关闭网络套接字。

    curl --insecure https://<your VM public IP address>:8443/exit
    

现在,你已看到具有自签名 TLS/SSL 证书的加载和演示操作,同时对应用进行一些微不足道的更改,以查看接受操作。

使用安全的出站连接来运行 Spring Boot 应用程序

在本部分中,将修改上一部分中的代码,以便出站连接的 TLS/SSL 证书来自 Azure 密钥库。 这样便可以通过 Azure Key Vault 来满足加载、呈现和接受操作的需要

  1. 将 Apache HTTP 客户端依赖项添加到 pom.xml 文件:

    <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
       <version>4.5.13</version>
    </dependency>
    
  2. 添加一个名为 ssl-test-outbound 的新 rest 终结点。 此终结点向自身打开一个 TLS 套接字,并验证 TLS 连接是否接受 TLS/SSL 证书。 将启动类的上一部分替换为以下代码。

    import java.security.KeyStore;
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import com.azure.security.keyvault.jca.KeyVaultLoadStoreParameter;
    import org.springframework.http.HttpStatus;
    import org.springframework.http.ResponseEntity;
    import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import org.apache.http.ssl.SSLContexts;
    
    @SpringBootApplication
    @RestController
    public class SsltestApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SsltestApplication.class, args);
        }
    
        @GetMapping(value = "/ssl-test")
        public String inbound(){
            return "Inbound TLS is working!!";
        }
    
        @GetMapping(value = "/ssl-test-outbound")
        public String outbound() throws Exception {
            KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault");
            KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter(
                System.getProperty("azure.keyvault.uri"));
            azureKeyVaultKeyStore.load(parameter);
            SSLContext sslContext = SSLContexts.custom()
                                               .loadTrustMaterial(azureKeyVaultKeyStore, null)
                                               .build();
    
            HostnameVerifier allowAll = (String hostName, SSLSession session) -> true;
            SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, allowAll);
    
            CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(csf)
                .build();
    
            HttpComponentsClientHttpRequestFactory requestFactory =
                new HttpComponentsClientHttpRequestFactory();
    
            requestFactory.setHttpClient(httpClient);
            RestTemplate restTemplate = new RestTemplate(requestFactory);
            String sslTest = "https://localhost:8443/ssl-test";
    
            ResponseEntity<String> response
                = restTemplate.getForEntity(sslTest, String.class);
    
            return "Outbound TLS " +
                (response.getStatusCode() == HttpStatus.OK ? "is" : "is not")  + " Working!!";
        }
    
        @GetMapping(value = "/exit")
        public void exit() {
            System.exit(0);
        }
    
    }
    
  3. 运行以下命令以编译代码并将其打包到可执行的 JAR 文件中。

    mvn clean package
    
  4. 使用本文前面所述的 sftp 命令再次上传应用。

    cd target
    sftp <your VM public IP address>
    put *.jar
    
  5. 在 VM 上运行应用。

    set -o noglob
    ssh azureuser@<your VM public IP address> "java -jar *.jar"
    
  6. 服务器运行后,验证服务器是否接受 TLS/SSL 证书。 在发出前面的 curl 命令的同一 Bash shell 中运行以下命令。

    curl --insecure https://<your VM public IP address>:8443/ssl-test-outbound
    

    应该看到消息 Outbound TLS is working!!

  7. 调用 exit 路径,终止服务器并关闭网络套接字。

    curl --insecure https://<your VM public IP address>:8443/exit
    

现在你已通过一个简单示例了解了如何使用存储在 Azure Key Vault 中的自签名 TLS/SSL 证书执行加载、呈现和接受操作

部署到 Azure Spring Apps

现在,你已在本地运行 Spring Boot 应用程序,现在可以将其移动到生产环境。 使用 Azure Spring Apps 可以轻松地将 Spring Boot 应用程序部署到 Azure,而无需进行任何代码更改。 该服务管理 Spring 应用程序的基础结构,让开发人员可以专注于代码。 Azure Spring Apps 可以通过以下方法提供生命周期管理:综合性监视和诊断、配置管理、服务发现、CI/CD 集成、蓝绿部署等。 若要将应用程序部署到 Azure Spring Apps,请参阅将 第一个应用程序部署到 Azure Spring Apps

后续步骤

若要了解有关 Spring 和 Azure 的详细信息,请继续访问“Azure 上的 Spring”文档中心。