1. 簡介
應用程式通常是透過網際網路交換資料。您的應用程式可能會與不信任的伺服器通訊,因此傳送及接收私密資訊時,請特別小心。
建構目標
在這個程式碼研究室中,您會建構一個用來顯示訊息的應用程式。每個訊息都會包含寄件者的名稱、簡訊和「個人資料相片」的網址。應用程式會執行下列動作來顯示這類訊息:
|
課程內容
- 安全網路通訊的重要性。
- 如何使用 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 檔案載入訊息清單、名稱和個人資料相片的網址。接下來,訊息會顯示在清單中,且應用程式會從參照的網址載入圖片。
注意:程式碼研究室中使用的應用程式僅供示範之用,需要處理的錯誤數量會低於實際工作環境。
應用程式架構
應用程式會按照 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"
建構並執行應用程式
- 啟動 Android Studio,並以 Android 專案的形式開啟 SecureConfig 目錄。
- 按一下「Run」即可啟動應用程式:
下方的應用程式螢幕截圖是裝置上顯示的內容:
4. 基本網路安全性設定
這個步驟會設定基本的網路安全性設定,並觀察違反設定規則時出現的錯誤。
總覽
「網路安全性設定」可讓應用程式透過「宣告設定檔」自訂網路安全性設定。這個 XML 檔案內含整個設定,無須變更程式碼。
允許下列設定:
- 選擇不採用明文流量:停用明文流量。
- 自訂信任錨點:指定應用程式信任的憑證授權單位和來源。
- 僅限偵錯的覆寫:安全地偵錯安全連線,不影響子版本的發布。
- 憑證綁定:限制特定憑證的安全連線。
可依照網域整理檔案,以便將網路安全性設定套用至所有網址,或只套用至特定網域。
「網路安全性設定」適用於 Android 7.0 (API 級別 24) 以上版本。
建立網路安全性設定 XML 檔案
建立名為 network_security_config.xml
的新 xml 資源檔案。
在左側的「Android Project Panel」上,在 res
上按一下滑鼠右鍵,然後選取「New」>「Android resource file」。
設定下列選項,然後按一下「OK」。
「File name」 |
|
「Resource type」 |
|
「Root element」 |
|
「Directory name」 |
|
開啟 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">
...
編譯並執行應用程式
編譯並執行應用程式。
您會看到錯誤訊息,表示應用程式嘗試透過明文連線載入資料!
您會在 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。在這個程式碼研究室中,我們會使用 http-server
Node.JS 模組,為應用程式提供資料。
- 開啟終端機並安裝
http-server
:
npm install http-server -g
- 前往您已查看程式碼的目錄,然後前往
server/
目錄:
cd server/
- 啟動網路伺服器,並提供資料/目錄中的檔案:
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
項目且與 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 提供資料:
- 在終端機視窗中變更
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 存取 localhost 伺服器。
變更 gradle.properties
檔案:
gradle.properties
postsUrl="https://localhost:8080/posts.json"
編譯並執行應用程式。
伺服器憑證無效,因此應用程式會失敗並顯示錯誤:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
伺服器使用自行簽署的憑證,但這算不上系統的一環,因此應用程式無法存取網路伺服器。我們會在接下來的步驟中為 localhost 網域新增這個自行簽署憑證,而不會停用 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>
這項設定會「停用明文網路流量」;如果是偵錯版本**,則會啟用系統提供的憑證授權單位,以及儲存在 res/raw
目錄中的憑證檔案。
注意:偵錯設定會間接新增 <certificates src="system" />
,因此即使沒有該段程式碼,應用程式也能正常運作。我們新增了這項設定,以向您顯示如何在進階設定中新增。
- 接著,請將檔案「
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
檔案:
執行應用程式
編譯並執行應用程式。應用程式現在會使用自行簽署的偵錯憑證,透過 HTTPS 存取我們的本機網路伺服器。
如果發生錯誤,請仔細檢查 Logcat 輸出內容,確認您已使用新的指令列選項重新啟動 http-server
。此外,請確認 debug_certificate.crt
檔案是否位於正確位置 (res/raw/debug_certificate.crt
)。
8. 瞭解詳情
「網路安全性設定」還支援許多進階功能,包括:
使用這些功能時,請參閱說明文件,進一步瞭解最佳做法和限制。
提高應用程式的安全性!
在本程式碼研究室中,您已瞭解如何運用「網路安全性設定」,提升 Android 應用程式的安全性。您可以思考如何讓自己的應用程式運用這些功能,以及如何透過更強大的偵錯設定來進行測試和開發。