Android 网络安全配置 Codelab

1. 简介

应用程序通过互联网交换数据是很常见的情况。因为您的应用程序可能会与它信任的服务器以外的服务器通信,所以在发送和接收可能敏感和私密的信息时,您需要谨慎。

您将构建什么

在本 Codelab 中,您将构建一个显示消息的应用程序。每条消息将包含发送者的姓名、文本消息以及其“个人资料图片”的 URL。该应用程序将通过以下方式显示这些消息

  • 加载包含从网络获取的消息列表的 JSON 文件。
  • 加载每个个人资料图片并在相应消息旁边显示它。

您将学到什么

  • 为什么安全的网络通信很重要。
  • 如何使用 Volley 库发出网络请求。
  • 如何使用**网络安全配置**来帮助使网络通信更安全。
  • 如何在开发和测试期间修改一些高级**网络安全配置**选项。
  • 探索最常见的网络安全问题之一,并了解**网络安全配置**如何帮助防止它。

您需要什么

  • 最新版本的 Android Studio
  • 运行 Android 7.0(API 级别 24)或更高版本的 Android 设备或模拟器
  • Node.js(或可配置 Web 服务器的访问权限)

如果您在完成此 Codelab 时遇到任何问题(代码错误、语法错误、措辞不清等),请通过 Codelab 左下角的“报告错误”链接报告问题。

2. 设置

下载代码

单击以下链接下载此 Codelab 的所有代码

解压缩下载的 zip 文件。这将解压缩一个根文件夹(android-network-secure-config),其中包含 Android Studio 项目(SecureConfig/)以及我们将在稍后阶段使用的一些数据文件(server/)。

您也可以直接从 GitHub 检查代码:(从master分支开始。)

我们还准备了一个分支,其中包含每个步骤之后的最终代码。如果您遇到困难,请查看 GitHub 上的分支,或克隆整个存储库:https://github.com/android/codelab-android-network-security-config/branches/all

3. 运行应用程序

单击“加载”图标后,此应用程序访问远程服务器以从 JSON 文件加载消息、名称和个人资料图片的 URL 列表。接下来,消息显示在列表中,并且应用程序从引用的 URL 加载图像。

注意:我们在此 Codelab 中使用的应用程序仅用于演示目的。它不包含生产环境中所需的那么多错误处理。

d9e465c94b420ea1.png

应用程序架构

该应用程序遵循 MVP 模式,将数据存储和网络访问(模型)与逻辑(演示者)和显示(视图)分离。

MainContract 类包含描述 View 和 Presenter 之间接口的契约

MainContract.java

/*
 * Copyright 2017 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.networksecurity;

import com.example.networksecurity.model.Post;

/**
 * Contract defining the interface between the View and Presenter.
 */
public interface MainContract {

    interface View {
        /**
         * Sets the presenter for interaction from the View.
         *
         * @param presenter
         */
        void setPresenter(Presenter presenter);

        /**
         * Displays or hides a loading indicator.
         *
         * @param isLoading If true, display a loading indicator, hide it otherwise.
         */
        void setLoadingPosts(boolean isLoading);

        /**
         * Displays a list of posts on screen.
         *
         * @param posts The posts to display. If null or empty, the list should not be shown.
         */
        void setPosts(Post[] posts);

        /**
         * Displays an error message on screen and optionally prints out the error to logcat.
         */
        void showError(String title, String error);

        /**
         * Hides the error message.
         *
         * @see #showError(String, String)
         */
        void hideError();

        /**
         * Displays an empty message and icon.
         *
         * @param showMessage If true, the message is show. If false, the message is hidden
         */
        void showNoPostsMessage(boolean showMessage);
    }

    interface Presenter {
        /**
         * Call to start the application. Sets up initial state.
         */
        void start();

        /**
         * Loads post for display.
         */
        void loadPosts();

        /**
         * An error was encountered during the loading of profile images.
         */
        void onLoadPostImageError(String error, Exception e);
    }

}

应用程序配置

出于演示目的,此应用程序已禁用所有网络缓存。理想情况下,在生产环境中,应用程序将利用本地缓存来限制远程网络请求的数量。

gradle.properties 文件包含加载消息列表的 URL

gradle.properties

postsUrl="http://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"

构建和运行应用程序

  1. 启动 Android Studio 并将 SecureConfig 目录作为 Android 项目打开。
  2. 单击“运行”以启动应用程序:e15973f44eed7cc2.png

以下应用程序截图显示了它在设备上的外观

63300e7e262bd161.png

4. 基本网络安全配置

在此步骤中,我们将设置基本的网络安全配置并观察违反配置中规则之一时发生的错误。

概述

网络安全配置允许应用程序通过声明性配置文件自定义其网络安全设置。整个配置都包含在此 XML 文件中,不需要进行任何代码更改。

它允许配置以下内容

  • **明文流量选择退出:**禁用明文流量。
  • **自定义信任锚:**指定应用程序信任的证书颁发机构和来源。
  • **仅限调试的覆盖:**安全地调试安全连接,而不会影响发布版本。
  • **证书固定:**将安全连接限制到特定证书。

该文件可以按域组织,允许将网络安全设置应用于所有 URL 或仅应用于特定域。

网络安全配置在 Android 7.0(API 级别 24)及更高版本上可用。

创建网络安全配置 XML 文件

创建一个名为network_security_config.xml的新xml 资源文件

在左侧的“Android 项目面板”中,右键单击res,然后选择**新建**>**Android 资源文件**。

35db6786b96a6980.png

设置以下选项,然后单击**确定**。

文件名

network_security_config.xml

资源类型

XML

根元素

network-security-config

目录名

xml

36ae9e950fe66f1c.png

打开文件xml/network_security_config.xml(如果它没有自动打开)。

将其内容替换为以下代码段

res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false" >
    </base-config>
</network-security-config>

此配置适用于应用程序的基本配置或默认安全配置,并禁用所有明文流量

启用网络安全配置

接下来,在AndroidManifest.xml文件中添加对应用程序配置的引用。

打开文件AndroidManifest.xml并在其中找到application元素。

首先,删除设置android:usesCleartextTraffic="true"属性的行。

接下来,将android:networkSecurityConfig属性添加到 AndroidManifest 中的application元素,并引用network_security_config XML 文件资源:@xml/network_security_config

删除和添加上述两个属性后,打开的 application 标签应如下所示

AndroidManifest.xml

<application
    android:networkSecurityConfig="@xml/network_security_config"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme"
    android:fullBackupContent="false"
    tools:ignore="GoogleAppIndexingWarning">
    ...

编译并运行应用程序

编译并运行应用程序。

您将看到一个错误 - 应用程序正在尝试通过明文连接加载数据!

98d8a173d5293742.png

在 logcat 中,您会注意到此错误

java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted

该应用程序没有加载数据,因为它仍然配置为从未加密的 HTTP 连接加载消息列表。 gradle.properties 文件中配置的 URL 指向一个不使用 TLS 的 HTTP 服务器!

让我们将此 URL 更改为使用不同的服务器并通过安全的 HTTPS 连接加载数据。

按如下方式更改gradle.properties文件

gradle.properties

postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"

(请注意 URL 中的 https 协议。)

您可能需要重建项目才能应用此更改。从菜单中,选择构建 > 重建

再次运行该应用程序。您现在将看到数据加载,因为网络请求使用 HTTPS 连接

63300e7e262bd161.png

5. 常见问题:服务器端更新

网络安全配置可以在应用程序通过不安全连接发出请求时防止漏洞。

网络安全配置解决的另一个常见问题是影响加载到 Android 应用程序中的 URL 的服务器端更改。例如,在我们的应用程序中,假设服务器开始为个人资料图片返回不安全的 HTTP URL 而不是安全的 HTTPS URL。然后,强制执行 HTTPS 连接的网络安全配置将引发异常,因为此要求在运行时将无法满足。

更新应用程序后端

您可能还记得,应用程序首先加载消息列表,每个消息都引用个人资料图片的 URL。

假设应用程序使用的的数据发生了更改,导致应用程序请求不同的图像 URL。让我们通过修改后端数据 URL 来模拟此更改。

按如下方式更改gradle.properties文件

gradle.properties

postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v2/posts.json"

(请注意路径中的“v2”!)

您可能需要重建项目才能应用此更改。从菜单中,选择构建 > 重建

您可以通过浏览器访问“新”后端以查看修改后的 JSON 文件。请注意,所有引用的 URL 都使用 HTTP 而不是 HTTPS。

运行应用程序并检查错误

编译并运行应用程序。

该应用程序加载消息,但图像未加载。检查应用程序和 logcat 中的错误消息以了解原因

a2a98a842e99168d.png

java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted

该应用程序仍然使用 HTTPS 访问 JSON 文件。但是,JSON 文件中个人资料图片的链接使用 HTTP 地址,因此应用程序尝试通过(不安全)HTTP 加载图像。

保护数据

网络安全配置已成功防止意外数据泄露。应用程序阻止连接尝试,而不是尝试访问不安全的数据。

想象一下这样的场景,在推出之前对后端的更改没有进行充分的测试。即使在应用程序发布后,将网络安全配置应用于您的 Android 应用程序也可以防止类似问题发生。

更改后端以修复应用程序

将后端 URL 更改为已“修复”的新版本。此示例通过使用正确的 HTTPS URL 引用个人资料图片来模拟修复。

gradle.properties文件中更改后端 URL 并刷新项目

gradle.properties

postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v3/posts.json"

(请注意路径中的v3!)

再次运行该应用程序。它现在按预期工作

63300e7e262bd161.png

6. 域特定配置

到目前为止,我们已在 base-config 中指定了网络安全配置,该配置将配置应用于应用尝试建立的所有连接。

可以通过指定 domain-config 元素来覆盖特定目标的此配置。 domain-config 为特定的一组域名声明配置选项。

让我们将应用中的网络安全配置更新为以下内容

res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>

此配置将 base-config 应用于所有域名,除了域名“localhost”及其子域名,这些域名将应用不同的配置。

这里,基本配置阻止了所有域名的明文流量。但域名配置会覆盖该规则,允许应用使用明文访问localhost

使用本地 HTTP 服务器进行测试

现在应用可以使用明文访问localhost,让我们启动一个本地 Web 服务器并测试此访问协议。

有各种工具可用于托管非常基本的 Web 服务器,包括 Node.JS、Python 和 Perl。在本 Codelab 中,我们将使用 http-server Node.JS 模块来服务应用的数据。

  1. 打开终端并安装 http-server
npm install http-server -g
  1. 导航到您已检出代码的目录,然后转到 server/ 目录
cd server/
  1. 启动 Web 服务器并服务位于 data/ 目录中的文件
http-server ./data -p 8080
  1. 打开 Web 浏览器并导航到 http://localhost:8080 以验证您是否可以访问文件并查看“posts.json”文件

934e48553bcc48e7.png

  1. 接下来,将端口 8080 从设备转发到本地机器。在另一个终端窗口中运行以下命令
adb reverse tcp:8080 tcp:8080

您的应用现在可以从 Android 设备访问“localhost:8080”。

  1. 更改应用中用于加载数据的 URL 以指向 localhost 上的新服务器。按如下方式更改 gradle.properties 文件:(请记住,更改此文件后,您可能需要执行 Gradle 项目同步。)

gradle.properties

postsUrl="http://localhost:8080/posts.json"
  1. 运行应用并验证数据是否从本地机器加载。您可以尝试修改 data/posts.json 文件并刷新应用,以确认新配置按预期工作。

63300e7e262bd161.png

补充说明 - 域名配置

应用于特定域名的配置选项在 domain-config 元素中定义。此元素可以包含多个 domain 条目,这些条目指定 domain-config 规则应应用于何处。如果多个 domain-config 元素包含类似的 domain 条目,则网络安全配置将根据匹配字符的数量选择要应用于给定 URL 的配置。使用包含与 URL 最多匹配字符(连续)的 domain 条目的配置。

域名配置可以应用于多个域名,也可以包含子域名。

以下示例显示了一个包含多个域名的网络安全配置。(我们没有更改我们的应用,这只是一个示例!)

<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">secure.example.com</domain>
        <domain includeSubdomains="true">cdn.example.com</domain>
        <trust-anchors>
            <certificates src="@raw/trusted_roots"/>
        </trust-anchors>
    </domain-config>
</network-security-config>

有关更多详细信息,请参阅 配置文件格式定义

7. 调试覆盖

在开发和测试旨在通过 HTTPS 发出请求的应用时,您可能需要将其连接到本地 Web 服务器或测试环境,就像我们在上一步中所做的那样。

网络安全配置中的 debug-override 选项允许您设置仅在应用以调试模式运行时(即,当 android:debuggable 为 true 时)才应用的安全选项,而不是为此用例添加允许明文流量的全面使用或修改代码。由于其明确的仅调试定义,这比使用条件代码要安全得多。Play 商店还阻止上传可调试应用,这使得此选项更加安全。

在本地 Web 服务器上启用 SSL

之前,我们启动了一个在端口 8080 上通过 HTTP 提供数据的本地 Web 服务器。我们现在将生成一个自签名 SSL 证书并使用它通过 HTTPS 提供数据

  1. 通过在终端窗口中更改到 server/ 目录,然后执行以下命令来生成证书:(如果您仍在运行 http-server,则可以通过按 [CTRL] + [C] 停止它。)
# Run these commands from inside the server/ directory!

# Create a certificate authority
openssl genrsa -out root-ca.privkey.pem 2048
# Sign the certificate authority
openssl req -x509 -new -nodes -days 100 -key root-ca.privkey.pem -out root-ca.cert.pem -subj "/C=US/O=Debug certificate/CN=localhost" -extensions v3_ca -config openssl_config.txt
# create DER format crt for Android
openssl x509 -outform der -in root-ca.cert.pem -out debug_certificate.crt

这会生成一个证书颁发机构,对其进行签名,并生成 Android 所需的 DER 格式的证书。

  1. 使用新生成的证书启动支持 HTTPS 的 Web 服务器
http-server ./data --ssl --cert root-ca.cert.pem --key root-ca.privkey.pem

更新后端 URL

更改应用以通过 HTTPS 访问 localhost 服务器。

更改 gradle.properties 文件

gradle.properties

postsUrl="https://localhost:8080/posts.json"

编译并运行应用程序。

应用将因错误而失败,因为服务器的证书无效

3bcce1390e354724.png

java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

应用无法访问 Web 服务器,因为服务器正在使用自签名证书,该证书未被视为系统的一部分而受信任。我们不会禁用 HTTPS,而是在下一步中为 localhost 域名添加此自签名证书。

引用自定义证书颁发机构

Web 服务器现在使用默认情况下任何设备都不接受的自签名证书颁发机构 (CA) 提供数据。如果您从浏览器访问服务器,您会注意到一个安全警告:https://localhost:8080

898b69ea4fe9bc21.png

接下来,我们将在网络安全配置中使用 debug-overrides 选项仅允许此自定义证书颁发机构用于 localhost 域名

  1. 更改 xml/network_security_config.xml 文件,使其包含以下内容

res/xml/network_security_config.xml

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="false" />
    <debug-overrides>
        <trust-anchors>
            <!-- Trust a debug certificate in addition to the system certificates -->
            <certificates src="system" />
            <certificates src="@raw/debug_certificate" />
        </trust-anchors>
    </debug-overrides>
</network-security-config>

此配置禁用明文网络流量,并且对于调试版本*,*启用系统提供的证书颁发机构以及存储在 res/raw 目录中的证书文件。

注意:调试配置隐式添加 <certificates src="system" />,因此即使没有它,应用也能工作。我们添加它是为了展示如何在更高级的配置中添加它。

  1. 接下来,将文件“debug_certificate.crt”从 server/ 目录复制到 Android Studio 中应用的 res/raw 资源目录。您也可以将文件拖放到 Android Studio 中的正确位置。

如果该目录不存在,您可能需要先创建它。

从 server/ 目录,您可以运行以下命令来执行此操作,否则使用文件管理器或 Android Studio 创建文件夹并将文件复制到正确的位置

mkdir  ../SecureConfig/app/src/main/res/raw/
cp debug_certificate.crt ../SecureConfig/app/src/main/res/raw/

Android Studio 现在将在 app/res/raw 下列出文件 debug_certificate.crt 文件

c3111ae17558e167.png

运行应用

编译并运行应用。应用现在正在使用自签名调试证书通过 HTTPS 访问我们的本地 Web 服务器。

如果遇到错误,请仔细检查 logcat 输出并确保已使用新的命令行选项重新启动了 http-server。还要检查 debug_certificate.crt 文件是否位于正确的位置 (res/raw/debug_certificate.crt)。

63300e7e262bd161.png

8. 了解更多

网络安全配置支持更多高级功能,包括以下功能

使用这些功能时,请查看文档以了解最佳实践和限制。

使您的应用更安全!

在本 Codelab 中,您学习了如何使用网络安全配置来提高 Android 应用的安全性。考虑一下您的应用如何利用这些功能以及如何从更强大的调试配置中受益以进行测试和开发。

了解更多