Android 是一個權限分隔的操做系統,其中每一個應用都有其獨特的系統標識(Linux 用戶 ID 和組 ID)。系統各部分也分隔爲不一樣的標識。Linux 據此將不一樣的應用以及應用與系統分隔開來。html
其餘更詳細的安全功能經過「權限」機制提供,此機制會限制特定進程能夠執行的具體操做,而且根據 URI 權限受權臨時訪問特定的數據段。java
本文檔介紹應用開發者能夠如何使用 Android 提供的安全功能。通常性的 Android 安全性概覽在「Android 開源項目」中提供。android
Android 安全架構的中心設計點是:在默認狀況下任何應用都沒有權限執行對其餘應用、操做系統或用戶有不利影響的任何操做。這包括讀取或寫入用戶的私有數據(例如聯繫人或電子郵件)、讀取或寫入其餘應用程序的文件、執行網絡訪問、使設備保持喚醒狀態等。sql
因爲每一個 Android 應用都是在進程沙盒中運行,所以應用必須顯式共享資源和數據。它們的方法是聲明須要哪些權限來獲取基本沙盒未提供的額外功能。應用以靜態方式聲明它們須要的權限,而後 Android 系統提示用戶贊成。shell
應用沙盒不依賴用於開發應用的技術。特別是,Dalvik VM 不是安全邊界,任何應用均可運行原生代碼(請參閱 Android NDK)。各種應用 — Java、原生和混合 — 以一樣的方式放在沙盒中,彼此採用相同程度的安全防禦。編程
全部 APK(.apk
文件)都必須使用證書籤署,其私鑰由開發者持有。此證書用於識別應用的做者。證書不須要由證書頒發機構簽署;Android 應用在理想狀況下能夠並且一般也是使用自簽名證書。證書在 Android 中的做用是識別應用的做者。這容許系統授予或拒絕應用對簽名級權限的訪問,以及授予或拒絕應用得到與另外一應用相同的 Linux 身份的請求。安全
在安裝時,Android 爲每一個軟件包提供惟一的 Linux 用戶 ID。此 ID 在軟件包在該設備上的使用壽命期間保持不變。在不一樣設備上,相同軟件包可能有不一樣的 UID;重要的是每一個軟件包在指定設備上的 UID 是惟一的。網絡
因爲在進程級實施安全性,所以任何兩個軟件包的代碼一般都不能在同一進程中運行,由於它們須要做爲不一樣的 Linux 用戶運行。您能夠在每一個軟件包的 AndroidManifest.xml
的 manifest
標記中使用 sharedUserId
屬性,爲它們分配相同的用戶 ID。這樣作之後,出於安全目的,兩個軟件包將被視爲同一個應用,具備相同的用戶 ID 和文件權限。請注意,爲保持安全性,只有兩個簽署了相同簽名(而且請求相同的 sharedUserId)的應用才被分配同一用戶 ID。架構
應用存儲的任何數據都會被分配該應用的用戶 ID,而且其餘軟件包一般沒法訪問這些數據。使用 getSharedPreferences(String, int)
、openFileOutput(String, int)
或 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)
建立新文件時,可使用 MODE_WORLD_READABLE
和/或 MODE_WORLD_WRITEABLE
標記容許任何其餘軟件包讀取/寫入文件。設置這些標記時,文件仍歸您的應用全部,但其全局讀取和/或寫入權限已適當設置,使任何其餘應用均可看見它。app
基本 Android 應用默認狀況下未關聯權限,這意味着它沒法執行對用戶體驗或設備上任何數據產生不利影響的任何操做。要利用受保護的設備功能,必須在應用清單中包含一個或多個 <uses-permission>
標記。
例如,須要監控傳入的短信的應用要指定:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.app.myapp" > <uses-permission android:name="android.permission.RECEIVE_SMS" /> ... </manifest>
如需瞭解有關不一樣保護級別權限的詳細信息,請參閱正常權限和危險權限。
若是您的應用在其清單中列出正常權限(即,不會對用戶隱私或設備操做形成很大風險的權限),系統會自動授予這些權限。若是您的應用在其清單中列出危險權限(即,可能影響用戶隱私或設備正常操做的權限),系統會要求用戶明確授予這些權限。Android 發出請求的方式取決於系統版本,而系統版本是應用的目標:
targetSdkVersion
是 23 或更高版本,則應用在運行時向用戶請求權限。用戶可隨時調用權限,所以應用在每次運行時均需檢查自身是否具有所需的權限。如需瞭解有關在應用中請求權限的詳細信息,請參閱使用系統權限培訓指南。targetSdkVersion
是 22 或更低版本,則系統會在用戶安裝應用時要求用戶授予權限。若是將新權限添加到更新的應用版本,系統會在用戶更新應用時要求授予該權限。用戶一旦安裝應用,他們撤銷權限的惟一方式是卸載應用。一般,權限失效會致使 SecurityException
被扔回應用。但不能保證每一個地方都是這樣。例如,sendBroadcast(Intent)
方法在數據傳遞到每一個接收者時會檢查權限,在方法調用返回後,即便權限失效,您也不會收到異常。但在幾乎全部狀況下,權限失效會記入系統日誌。
Android 系統提供的權限請參閱 Manifest.permission
。此外,任何應用均可定義並實施本身的權限,所以這不是全部可能權限的詳盡列表。
可能在程序運行期間的多個位置實施特定權限:
隨着時間的推移,平臺中可能會加入新的限制,要想使用特定 API,您的應用可能必須請求以前不須要的權限。由於現有應用假設可隨意獲取這些 API 應用的訪問權限,因此 Android 可能會將新的權限請求應用到應用清單,以避免在新平臺版本上中斷應用。Android 將根據爲 targetSdkVersion
屬性提供的值決定應用是否須要權限。若是該值低於在其中添加權限的版本,則 Android 會添加該權限。
例如,API 級別 4 中加入了 WRITE_EXTERNAL_STORAGE
權限,用以限制訪問共享存儲空間。若是您的 targetSdkVersion
爲 3 或更低版本,則會向更新 Android 版本設備上的應用添加此權限。
注意:若是某權限自動添加到應用,則即便您的應用可能實際並不須要這些附加權限,Google Play 上的應用列表也會列出它們。
爲避免這種狀況,而且刪除您不須要的默認權限,請始終將 targetSdkVersion
更新至最高版本。可在 Build.VERSION_CODES
文檔中查看各版本添加的權限。
系統權限分爲幾個保護級別。須要瞭解的兩個最重要保護級別是正常權限和危險權限:
有許多權限其行爲方式與正常權限及危險權限都不一樣。SYSTEM_ALERT_WINDOW
和 WRITE_SETTINGS
特別敏感,所以大多數應用不該該使用它們。若是某應用須要其中一種權限,必須在清單中聲明該權限,而且發送請求用戶受權的 intent。系統將向用戶顯示詳細管理屏幕,以響應該 intent。
如需瞭解有關如何請求這些權限的詳情,請參閱 SYSTEM_ALERT_WINDOW
和 WRITE_SETTINGS
參考條目。
全部危險的 Android 系統權限都屬於權限組。若是設備運行的是 Android 6.0(API 級別 23),而且應用的 targetSdkVersion
是 23 或更高版本,則當用戶請求危險權限時系統會發生如下行爲:
READ_CONTACTS
權限,系統對話框只說明該應用須要訪問設備的聯繫信息。若是用戶批准,系統將嚮應用授予其請求的權限。READ_CONTACTS
權限,而後它又請求 WRITE_CONTACTS
,系統將當即授予該權限。任何權限均可屬於一個權限組,包括正常權限和應用定義的權限。但權限組僅當權限危險時才影響用戶體驗。能夠忽略正常權限的權限組。
若是設備運行的是 Android 5.1(API 級別 22)或更低版本,而且應用的 targetSdkVersion
是 22 或更低版本,則系統會在安裝時要求用戶授予權限。再次強調,系統只告訴用戶應用須要的權限組,而不告知具體權限。
表 1. 危險權限和權限組。
權限組 | 權限 |
---|---|
CALENDAR |
|
CAMERA |
|
CONTACTS |
|
LOCATION |
|
MICROPHONE |
|
PHONE |
|
SENSORS |
|
SMS |
|
STORAGE |
要實施您本身的權限,必須先使用一個或多個 <permission>
元素在 AndroidManifest.xml
中聲明它們。
例如,想要控制誰能夠開始其中一個 Activity 的應用可以下所示聲明此操做的權限:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp" > <permission android:name="com.example.myapp.permission.DEADLY_ACTIVITY" android:label="@string/permlab_deadlyActivity" android:description="@string/permdesc_deadlyActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" /> ... </manifest>
注:系統不容許多個軟件包使用同一名稱聲明權限,除非全部軟件包都使用同一證書籤署。若是軟件包聲明權限,則系統不容許用戶安裝具備相同權限名稱的其餘軟件包,除非這些軟件包使用與第一個軟件包相同的證書籤署。爲避免命名衝突,建議對自定義權限使用相反域名樣式命名,例如 com.example.myapp.ENGAGE_HYPERSPACE
。
protectionLevel
屬性是必要屬性,用於指示系統如何向用戶告知須要權限的應用,或者誰能夠擁有該權限,具體如連接的文檔中所述。
android:permissionGroup
屬性是可選屬性,只是用於幫助系統向用戶顯示權限。大多數狀況下,您要將此設爲標準系統組(列在 android.Manifest.permission_group
中),但您也能夠本身定義一個組。建議使用現有的組,由於這樣可簡化向用戶顯示的權限 UI。
須要爲權限提供標籤和描述。這些是用戶在查看權限列表(
)或單一權限詳細信息(android:label
)時能夠看到的字符串資源。標籤應簡短;用幾個詞描述權限保護的功能的關鍵部分。描述應該用幾個句子描述權限容許持有人執行的操做。咱們的約定是用兩個句子描述:第一句描述權限,第二句向用戶提醒爲應用授予權限後可能出現的錯誤類型。android:description
下面是 CALL_PHONE 權限的標籤和描述示例:
<string name="permlab_callPhone">directly call phone numbers</string> <string name="permdesc_callPhone">Allows the application to call phone numbers without your intervention. Malicious applications may cause unexpected calls on your phone bill. Note that this does not allow the application to call emergency numbers.</string>
您可使用 Settings 應用和 shell 命令 adb shell pm list permissions
查看系統中當前定義的權限。要使用 Settings 應用,請轉到 Settings > Applications。選擇一個應用並向下滾動查看該應用使用的權限。對於開發者,adb '-s' 選項以相似於用戶將會看到的形式顯示權限:
$ adb shell pm list permissions -s All Permissions: Network communication: view Wi-Fi state, create Bluetooth connections, full Internet access, view network state Your location: access extra location provider commands, fine (GPS) location, mock location sources for testing, coarse (network-based) location Services that cost you money: send SMS messages, directly call phone numbers ...
應用能夠定義本身的自定義權限,並經過定義 <uses-permission>
元素請求其餘應用的自定義權限。不過,您應該仔細評估您的應用是否有必要這樣作。
<uses-permission>
元素請求這些權限。您能夠經過 AndroidManifest.xml
應用高級權限,限制訪問系統或應用的所有組件。要執行此操做,在所需的組件上包含 android:permission
屬性,爲用於控制訪問它的權限命名。
Activity
權限(應用於 <activity>
標記)限制誰能夠啓動相關的 Activity。在 Context.startActivity()
和 Activity.startActivityForResult()
時會檢查權限;若是調用方沒有所需的權限,則調用會拋出 SecurityException
。
Service
權限(應用於 <service>
標記)限制誰能夠啓動或綁定到相關的服務。在 Context.startService()
、Context.stopService()
和 Context.bindService()
時會檢查權限;若是調用方沒有所需的權限,則調用會拋出 SecurityException
。
BroadcastReceiver
權限(應用於 <receiver>
標記)限制誰能夠發送廣播給相關的接收方。在 Context.sendBroadcast()
返回後檢查權限,由於系統會嘗試將提交的廣播傳遞到指定的接收方。所以,權限失效不會致使向調用方拋回異常;只是不會傳遞該 intent。一樣,能夠向 Context.registerReceiver()
提供權限來控制誰能夠廣播到以編程方式註冊的接收方。另外一方面,能夠在調用 Context.sendBroadcast()
時提供權限來限制容許哪些 BroadcastReceiver 對象接收廣播(請參閱下文)。
ContentProvider
權限(應用於 <provider>
標記)限制誰能夠訪問 ContentProvider
中的數據。(內容提供程序有重要的附加安全工具可用,稱爲 URI 權限,將在後面介紹。)與其餘組件不一樣,您能夠設置兩個單獨的權限屬性:android:readPermission
限制誰能夠讀取提供程序,android:writePermission
限制誰能夠寫入提供程序。請注意,若是提供程序有讀取和寫入權限保護,僅擁有寫入權限並不表示您能夠讀取提供程序。第一次檢索提供程序時將會檢查權限(若是沒有任何權限,將會拋出 SecurityException),對提供程序執行操做時也會檢查權限。使用 ContentResolver.query()
須要擁有讀取權限;使用 ContentResolver.insert()
、ContentResolver.update()
、ContentResolver.delete()
須要寫入權限。在全部這些狀況下,沒有所需的權限將致使調用拋出 SecurityException
。
除了實施誰能夠向註冊的 BroadcastReceiver
發送 intent 的權限(如上所述),您還能夠指定在發送廣播時須要的權限。經過使用權限字符串調用 Context.sendBroadcast()
,您能夠要求接收方的應用必須擁有該權限纔可接收您的廣播。
請注意,接收者和廣播者可能須要權限。此時,這兩項權限檢查都必須經過後方可將 intent 傳遞到相關的目標。
可對任何服務調用實施任意細化的權限。這可經過 Context.checkCallingPermission()
方法完成。使用所需的權限字符串調用,它將返回一個整數,表示權限是否已授予當前的調用進程。請注意,僅在執行從另外一個進程傳入的調用(一般是經過從服務發佈的 IDL 界面或者指定給另外一進程的某種其餘方式完成)時纔可以使用此方法。
檢查權限還有許多其餘有用的方法。若是您有另外一個進程的 pid,可使用 Context 方法 Context.checkPermission(String, int, int)
檢查針對該 pid 的權限。若是您有另外一個應用的軟件包名稱,可使用直接的 PackageManager 方法 PackageManager.checkPermission(String, String)
瞭解是否已爲特定軟件包授予特定權限。
到目前爲止所述的是標準權限系統,內容提供程序僅僅使用此係統一般是不夠的。內容提供程序可能須要經過讀取和寫入權限保護本身,而其直接客戶端也須要將特定 URI 傳給其餘應用以便於它們運行。郵件應用中的附件是一個典型的示例。應經過權限保護對郵件的訪問,由於這是敏感的用戶數據。可是,若是將圖像附件的 URI 提供給圖像查看程序,該圖像查看程序不會有打開附件的權限,由於它沒有理由擁有訪問全部電子郵件的權限。
此問題的解決方法是採用 per-URI 權限機制:在啓動 Activity 或返回結果給 Activity 時,調用方能夠設置 Intent.FLAG_GRANT_READ_URI_PERMISSION
和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION
。這將授予接收 Activity 權限訪問 intent 中的特定數據 URI,而無論它是否具備訪問 intent 對應的內容提供程序中數據的任何權限。
此機制支持常見的能力式模型,其中用戶交互(打開附件、從列表中選擇聯繫人等)驅動臨時授予細化的權限。這是一項關鍵功能,可將應用所需的權限縮小至只與其行爲直接相關的權限。
但授予細化的 URI 權限須要與擁有這些 URI 的內容提供程序進行必定的合做。強烈建議內容提供程序實施此功能,而且經過 android:grantUriPermissions
屬性或 <grant-uri-permissions>
標記聲明支持此功能。
在 Context.grantUriPermission()
、Context.revokeUriPermission()
和 Context.checkUriPermission()
方法中能夠找到更多信息。