1. 简介
应用通过互联网交换数据早已成为司空见惯的事情。由于应用可能与其不信任的服务器通信,因此在发送和接收可能属于敏感和隐私性质的信息时须格外小心。
构建内容
在此 Codelab 中,您将构建一个显示消息的应用。每条消息都应包含发送者的姓名、文本消息和指向其“个人资料照片”的网址。该应用将通过执行以下操作来显示这些消息:
|
学习内容
- 网络通信安全为什么很重要。
- 如何使用 Volley 库发出网络请求。
- 如何使用网络安全配置来帮助提高网络通信的安全性。
- 如何修改一些高级网络安全配置选项,帮助进行开发和测试。
- 了解最常见的网络安全问题之一,以及网络安全配置可以如何帮助防止这一问题发生。
所需条件
- 最新版本的 Android Studio
- 运行 Android 7.0(API 级别 24)或更高版本的 Android 设备或模拟器
- Node.js(或可配置的网络服务器的访问权限)
如果您在学习本 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 文件加载消息、姓名和指向其“个人资料照片”的网址的列表。接下来,消息以列表形式显示,应用会从所引用的网址加载图像。
注意:我们在此 Codelab 中使用的应用仅用于演示目的。其所需错误处理量会少于生产环境中的错误处理量。
应用架构
该应用遵循 MVP 模式,以便将数据存储和网络访问 (model) 与逻辑 (presenter) 和显示 (view) 分开。
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
文件包含用以加载消息列表的网址:
gradle.properties
postsUrl="http://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"
构建和运行应用
- 启动 Android Studio 并将 SecureConfig 目录作为 Android 项目打开。
- 点击“运行”以启动应用:
以下应用屏幕截图为其在设备上显示的样子:
4. 基本网络安全配置
在这一步中,我们将设置基本的网络安全配置,并观察违反配置中的任何规则时发生的错误。
概览
借助网络安全配置,应用可以通过声明性配置文件自定义其网络安全设置。整个配置都包含在此 XML 文件中,并且无需更改代码。
它支持对以下内容进行配置:
- 选择停用明文流量:停用明文流量。
- 自定义信任锚:指定应用信任的证书授权机构和来源。
- 仅调试替换:在不影响发布 build 的情况下,安全地调试安全连接。
- 证书锁定:限制仅与特定证书建立安全连接。
该文件可以按网域划分,从而将网络安全设置应用于所有网址或仅应用到特定网域。
网络安全配置适用于 Android 7.0(API 级别 24)及更高版本。
创建网络安全配置 XML 文件
创建名为 network_security_config.xml
的新 xml 资源文件。
在左侧的 Android Project 面板中,右键点击 res
,然后选择 New > Android 资源文件。
设置以下选项,然后点击 OK。
文件名 |
|
资源类型 |
|
根元素 |
|
目录名称 |
|
打开 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
删除并添加以上两个属性后,起始应用标记应如下所示:
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">
...
编译并运行应用
编译并运行应用。
您将看到一条错误提示,表示该应用尝试通过明文连接加载数据!
在 logcat 中,您会注意到以下错误:
java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted
应用未加载数据,因为它仍然配置为从未加密的 HTTP 连接加载消息列表。在 gradle.properties
文件中配置的网址指向一个不使用 TLS 的 HTTP 服务器!
我们将此网址更改为其他服务器,并通过安全的 HTTPS 连接加载数据。
按如下方式更改 gradle.properties
文件:
gradle.properties
postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v1/posts.json"
(注意网址中的 https 协议。)
您可能需要重新构建项目才能使此更改生效。从菜单中选择 Build > Rebuild
。
再次运行应用。现在,您将看到数据加载,因为网络请求使用了 HTTPS 连接:
5. 常见问题:服务器端更新
当应用通过不安全的连接发出请求时,网络安全配置可以防范漏洞。
网络安全配置解决的另一个常见问题是,服务器端变更会影响加载到 Android 应用中的网址。例如,在我们的应用中,假设服务器开始为个人资料图片返回不安全的 HTTP 网址,而不是安全的 HTTPS 网址。然后,强制执行 HTTPS 连接的网络安全配置将引发异常,原因是在运行时无法满足此要求。
更新应用后端
您可能还记得,应用首先会加载一个消息列表,每条消息都会引用个人资料照片的网址。
假设应用消耗的数据发生了变化,导致应用请求不同的图片网址。我们可以通过修改后端数据网址来模拟此更改。
按如下方式更改 gradle.properties
文件:
gradle.properties
postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v2/posts.json"
(请注意路径中的“v2”!)
您可能需要重新构建项目才能使此更改生效。从菜单中选择 Build > Rebuild
。
您可以从浏览器中访问“新”后端,以查看修改后的 JSON 文件。请注意,所有引用的网址都使用 HTTP 而非 HTTPS。
运行应用并检查错误
编译并运行应用。
应用可以加载消息,但无法加载图片。检查应用和 logcat 中的错误消息,了解原因:
java.io.IOException: Cleartext HTTP traffic to storage.googleapis.com not permitted
应用仍使用 HTTPS 访问 JSON 文件。然而,JSON 文件中指向个人资料图片的链接使用的是 HTTP 地址,因此应用会尝试通过(不安全的)HTTP 加载图片。
保护数据
网络安全配置已成功阻止了意外的数据泄露。应用阻止了连接尝试,而不是尝试访问不安全的数据。
设想这样一个场景,即后端的更改在发布之前未经过充分测试。将网络安全配置应用到您的 Android 应用中,可以在出现类似问题时及时捕捉问题,即使在应用发布之后也是如此。
更改后端以修复应用
将后端网址更改为已“修复”的新版本。本示例使用正确的 HTTPS 网址引用配置文件图像来模拟修复。
更改 gradle.properties
文件中的后端网址并刷新项目:
gradle.properties
postsUrl="https://storage.googleapis.com/network-security-conf-codelab.appspot.com/v3/posts.json"
(请注意路径中的 v3!)
再次运行应用。现在,它将按预期运行:
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,我们将启动本地网络服务器并测试此访问协议。
Node.JS、Python 和 Perl 等多种工具都可用于托管非常基本的网络服务器。在此 Codelab 中,我们将使用 http-server
Node.JS 模块为应用提供数据。
- 打开终端并安装
http-server
:
npm install http-server -g
- 导航到您已签出代码的目录,然后转到
server/
目录:
cd server/
- 启动网络服务器,并提供位于 data/ 目录下的文件:
http-server ./data -p 8080
- 打开网络浏览器并导航到 http://localhost:8080,以验证您能否访问这些文件并查看“
posts.json
”文件:
- 接下来,将端口 8080 从设备转接到本地计算机。在其他终端窗口中运行以下命令:
adb reverse tcp:8080 tcp:8080
您的应用现在可以从 Android 设备访问“localhost:8080”。
- 更改用于在应用中加载数据的网址,以指向
localhost
上的新服务器。按如下所示更改gradle.properties
文件:(请注意,更改此文件后,您可能需要执行 gradle 项目同步。)
gradle.properties
postsUrl="http://localhost:8080/posts.json"
- 运行应用并验证数据是否从本地计算机加载。您可以尝试修改
data/posts.json
文件并刷新应用,以确认新配置是否按预期运行。
补充内容 - 网域配置
适用于特定网域的配置选项在 domain-config
元素中定义。此元素可以包含多个 domain
条目,以指定 domain-config
规则应该应用到的位置。如果多个 domain-config
元素包含类似的 domain
条目,则网络安全配置会根据匹配字符数量选择要应用于给定网址的配置。使用的配置包含与网址匹配最多(连续)字符的 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 发出请求的应用时,您可能需要像在上一步所做的那样,将其连接到本地网络服务器或测试环境。
您不需要为此用例允许使用明文流量或是修改代码,网络安全配置中的 debug-override
选项允许您设置仅当应用在调试模式下运行时才适用的安全选项,也就是当 android:debuggable
为 true 时。相比使用条件代码,安全性显著提升,因为其具有明确的仅调试定义。Play 商店也会阻止上传可调试的应用,这使得此选项更加安全。
在本地网络服务器上启用 SSL
之前,我们启动了本地网络服务器,它在端口 8080 通过 HTTP 提供数据。我们现在将生成一个自签名 SSL 证书,并使用该证书通过 HTTPS 传送数据:
- 通过在终端窗口中切换到
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 格式的证书。
- 使用新生成的证书通过 HTTPS 启动网络服务器:
http-server ./data --ssl --cert root-ca.cert.pem --key root-ca.privkey.pem
更新后端网址
更改应用以通过 HTTPS 访问本地主机服务器。
更改 gradle.properties
文件:
gradle.properties
postsUrl="https://localhost:8080/posts.json"
编译并运行应用。
由于服务器证书无效,应用将运行失败:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
应用无法访问网络服务器,因为服务器使用的自签名证书不被信任为系统的一部分。我们会在下一步中为本地主机网域添加自签名证书,而不是停用 HTTPS。
引用自定义证书授权机构
现在,网络服务器使用默认不被任何设备接受的自签名证书授权机构 (CA) 来提供数据。如果您通过浏览器访问服务器,会发现安全警告:https://localhost:8080
接下来,我们将使用网络安全配置中的 debug-overrides
选项,仅允许 localhost
网域使用此自定义证书授权机构:
- 更改
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>
此配置会停用明文网络流量,而对于调试 build,会启用系统提供的证书授权机构,以及存储在 res/raw
目录中的证书文件。
注意:调试配置会隐式添加 <certificates src="system" />
,因此即使不执行此操作,应用也可以运行。我们添加它是为了向您展示在更高级配置中的添加方式。
- 接下来,从
server/
目录下将文件“debug_certificate.crt
”复制到 Android Studio 中应用的res/raw
资源目录中。您还可以将文件拖放到 Android Studio 中的正确位置。
如果此目录不存在,您可能需要先创建此目录。
在服务器/ 目录中,您可以运行以下命令来执行此操作,否则,请使用文件管理器或 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
文件:
运行应用
编译并运行应用。应用现在使用自签名调试证书通过 HTTPS 访问我们的本地网络服务器。
如果您遇到错误,请仔细查看 logcat 输出并确保已使用新的命令行选项重新启动 http-server
。同时,请确认 debug_certificate.crt
文件位于正确的位置 (res/raw/debug_certificate.crt
)。
8. 了解详情
网络安全配置支持更多高级功能,包括:
使用这些功能时,请查看文档以了解最佳实践和限制的详细信息。
提升应用的安全性!
在本 Codelab 中,您已了解了如何使用网络安全配置来提高 Android 应用的安全性。考虑一下您自己的应用如何使用这些功能,以及在测试和开发中如何受益于功能更强大的调试配置。