Android 網路安全性設定程式碼研究室

1. 簡介

應用程式通常是透過網際網路交換資料。您的應用程式可能會與不信任的伺服器通訊,因此傳送及接收私密資訊時,請特別小心。

建構目標

在這個程式碼研究室中,您會建構一個用來顯示訊息的應用程式。每個訊息都會包含寄件者的名稱、簡訊和「個人資料相片」的網址。應用程式會執行下列動作來顯示這類訊息:

  • 載入內含網路簡訊清單的 JSON 檔案。
  • 載入每一張個人資料相片,並顯示在相對應的訊息旁邊。

課程內容

  • 安全網路通訊的重要性。
  • 如何使用 Volley 程式庫發出網路要求。
  • 如何使用網路安全性設定,提升網路通訊的安全性。
  • 如何修改進階的網路安全性設定選項,在開發與測試過程中產生效益。
  • 探索最常見的網路安全性問題,並瞭解網路安全性設定如何避免該問題。

軟硬體需求

  • 最新版 Android Studio
  • 搭載 Android 7.0 (API 級別 24) 以上版本的 Android 裝置或模擬器
  • Node.js (或可設定網路伺服器的存取權)

在執行程式碼研究室各項步驟的過程中,如果您遇到任何問題 (例如程式碼錯誤、文法錯誤或用詞不明確等),請透過程式碼研究室左下角的「回報錯誤」連結來回報問題。

2. 開始設定

下載程式碼

點選下方連結即可下載這個程式碼研究室的所有程式碼:

將下載的 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 檔案載入訊息清單、名稱和個人資料相片的網址。接下來,訊息會顯示在清單中,且應用程式會從參照的網址載入圖片。

注意:程式碼研究室中使用的應用程式僅供示範之用,需要處理的錯誤數量會低於實際工作環境。

d9e465c94b420ea1.png

應用程式架構

應用程式會按照 MVP 模式,將資料儲存、網路存取 (模型) 與邏輯 (呈現工具)、螢幕 (檢視畫面) 區隔開來。

MainContract 類別包含一份合約,描述「檢視畫面」和「呈現工具」之間的介面:

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"

建構並執行應用程式

  1. 啟動 Android Studio,並以 Android 專案的形式開啟 SecureConfig 目錄。
  2. 按一下「Run」即可啟動應用程式:e15973f44eed7cc2.png

下方的應用程式螢幕截圖是裝置上顯示的內容:

63300e7e262bd161.png

4. 基本網路安全性設定

這個步驟會設定基本的網路安全性設定,並觀察違反設定規則時出現的錯誤。

總覽

「網路安全性設定」可讓應用程式透過「宣告設定檔」自訂網路安全性設定。這個 XML 檔案內含整個設定,無須變更程式碼。

允許下列設定:

  • 選擇不採用明文流量:停用明文流量。
  • 自訂信任錨點:指定應用程式信任的憑證授權單位和來源。
  • 僅限偵錯的覆寫:安全地偵錯安全連線,不影響子版本的發布。
  • 憑證綁定:限制特定憑證的安全連線。

可依照網域整理檔案,以便將網路安全性設定套用至所有網址,或只套用至特定網域。

「網路安全性設定」適用於 Android 7.0 (API 級別 24) 以上版本。

建立網路安全性設定 XML 檔案

建立名為 network_security_config.xml 的新 xml 資源檔案

在左側的「Android Project Panel」上,在 res 上按一下滑鼠右鍵,然後選取「New」>「Android resource file」。

35db6786b96a6980.png

設定下列選項,然後按一下「OK」。

「File name」

network_security_config.xml

「Resource type」

XML

「Root element」

network-security-config

「Directory name」

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>

這項設定適用於應用程式的「基本設定」(或預設的安全性設定),而且會「停用所有明文流量」。

啟用「Network Security Configuration」

接著,請在 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">
    ...

編譯並執行應用程式

編譯並執行應用程式。

您會看到錯誤訊息,表示應用程式嘗試透過明文連線載入資料!

98d8a173d5293742.png

您會在 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 連線:

63300e7e262bd161.png

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 中的錯誤訊息,以瞭解原因:

a2a98a842e99168d.png

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!)

再次執行應用程式。現在應用程式可以正常運作:

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 後,接著啟動本機網路伺服器,並測試這個存取權通訊協定。

有許多工具可用來代管非常基本的網路伺服器,包括 Node.JS、Python 和 Perl。在這個程式碼研究室中,我們會使用 http-server Node.JS 模組,為應用程式提供資料。

  1. 開啟終端機並安裝 http-server
npm install http-server -g
  1. 前往您已查看程式碼的目錄,然後前往 server/ 目錄:
cd server/
  1. 啟動網路伺服器,並提供資料/目錄中的檔案:
http-server ./data -p 8080
  1. 開啟網路瀏覽器並前往 http://localhost:8080,確認您可以存取檔案以及查看「posts.json」檔案:

934e48553bcc48e7.png

  1. 接著,請將裝置的通訊埠 8080 轉送到本機電腦。在其他終端機視窗中執行下列指令:
adb reverse tcp:8080 tcp:8080

您的應用程式隨即會透過 Android 裝置存取「localhost:8080」。

  1. 變更在應用程式中用來載入資料的網址,指向 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 項目,「網路安全性設定」會根據比對的字元數,來選擇要套用至特定網址的設定。系統會使用包含 domain 項目且與 URL 有最多字元相符 (連續) 的設定。

網域設定可以套用至多個網域,但也可能包含子網域。

以下範例會顯示包含多個網域的「網路安全性設定」。(我們不會變更應用程式,這只是一個例子!)

<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 提供資料:

  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 啟動網路伺服器:
http-server ./data --ssl --cert root-ca.cert.pem --key root-ca.privkey.pem

更新後端網址

變更應用程式即可透過 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.

伺服器使用自行簽署的憑證,但這算不上系統的一環,因此應用程式無法存取網路伺服器。我們會在接下來的步驟中為 localhost 網域新增這個自行簽署憑證,而不會停用 HTTPS。

參照自訂憑證授權單位

網路伺服器現在會透過自行簽署的憑證授權單位 (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 中的正確位置。

如果目錄不存在,則可能需要先建立這個目錄。

您可以在伺服器/目錄中執行下列指令;否則,請使用檔案管理員或 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 存取我們的本機網路伺服器。

如果發生錯誤,請仔細檢查 Logcat 輸出內容,確認您已使用新的指令列選項重新啟動 http-server。此外,請確認 debug_certificate.crt 檔案是否位於正確位置 (res/raw/debug_certificate.crt)。

63300e7e262bd161.png

8. 瞭解詳情

「網路安全性設定」還支援許多進階功能,包括:

使用這些功能時,請參閱說明文件,進一步瞭解最佳做法和限制。

提高應用程式的安全性!

在本程式碼研究室中,您已瞭解如何運用「網路安全性設定」,提升 Android 應用程式的安全性。您可以思考如何讓自己的應用程式運用這些功能,以及如何透過更強大的偵錯設定來進行測試和開發。

瞭解詳情