Android系統許可權

葉應是葉發表於2017-02-18

Android 是一個許可權分隔的作業系統,其中每個應用都有其獨特的系統標識(Linux 使用者 ID 和組 ID)。系統各部分也分隔為不同的標識。Linux 據此將不同的應用之間、應用與系統之間分隔開來

一、安全架構

Android 安全架構的中心設計點是:在預設情況下任何應用都沒有許可權執行對其他應用、作業系統或使用者有不利影響的任何操作。這包括讀取或寫入使用者的私有資料(例如聯絡人或電子郵件)、讀取或寫入其他應用程式的檔案、執行網路訪問、使裝置保持喚醒狀態等

由於每個 Android 應用都是在程式沙盒中執行,因此應用必須顯式共享資源和資料。它們的方法是宣告需要哪些許可權來獲取基本沙盒未提供的額外功能。應用以靜態方式宣告它們需要的許可權,然後 Android 系統提示使用者同意

應用沙盒不依賴用於開發應用的技術。特別是,Dalvik VM 不是安全邊界,任何應用都可執行原生程式碼(請參閱 Android NDK)。各類應用:Java、原生和混合,以同樣的方式放在沙盒中,彼此採用相同程度的安全防護

二、應用簽署

所有 APK(.apk 檔案)都必須使用證照籤署,其私鑰由開發者持有。此證照用於識別應用的作者。證照不需要由證照頒發機構簽署,Android 應用在理想情況下可以而且通常也是使用自簽名證照。證照在 Android 中的作用是識別應用的作者。這允許系統授予或拒絕應用對簽名級許可權的訪問,以及授予或拒絕應用獲得與另一應用相同的 Linux 身份的請求

三、使用者 ID 和檔案訪問

在安裝時,Android 為每個軟體包提供唯一的 Linux 使用者 ID。此 ID 在軟體包在該裝置上的使用壽命期間保持不變。在不同裝置上,相同軟體包可能有不同的 UID;重要的是每個軟體包在指定裝置上的 UID 是唯一的

由於在程式級實施安全性,因此任何兩個軟體包的程式碼通常都不能在同一程式中執行,因為它們需要作為不同的 Linux 使用者執行。您可以在每個軟體包的 AndroidManifest.xmlmanifest 標記中使用 sharedUserId 屬性,為它們分配相同的使用者 ID。這樣做以後,出於安全目的,兩個軟體包將被視為同一個應用,具有相同的使用者 ID 和檔案許可權。請注意,為保持安全性,只有兩個簽署了相同簽名(並且請求相同的 sharedUserId)的應用才被分配同一使用者 ID

應用儲存的任何資料都會被分配該應用的使用者 ID,並且其他軟體包通常無法訪問這些資料。使用 getSharedPreferences(String, int)openFileOutput(String, int)openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory) 建立新檔案時,可以使用 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 標記允許任何其他軟體包讀取/寫入檔案。設定這些標記時,檔案仍歸你的應用所有,但其全域性讀取和/或寫入許可權已適當設定,使任何其他應用都可看見它

四、使用許可權

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 發出請求的方式取決於系統版本,而系統版本是應用的目標:

  • 如果裝置執行的是 Android 6.0(API 級別 23)或更高版本,並且應用的 targetSdkVersion23 或更高版本,則應用在執行時向使用者請求許可權。使用者可隨時呼叫許可權,因此應用在每次執行時均需檢查自身是否具備所需的許可權
  • 如果裝置執行的是 Android 5.1(API 級別 22)或更低版本,並且應用的 targetSdkVersion22 或更低版本,則系統會在使用者安裝應用時要求使用者授予許可權。如果將新許可權新增到更新的應用版本,系統會在使用者更新應用時要求授予該許可權。使用者一旦安裝應用,他們撤銷許可權的唯一方式是解除安裝應用

通常,許可權失效會導致 SecurityException 發生。但不能保證每個地方都是這樣。例如,sendBroadcast(Intent) 方法在資料傳遞到每個接收者時會檢查許可權,在方法呼叫返回後,即使許可權失效,您也不會收到異常。但在幾乎所有情況下,許可權失效會記入系統日誌

Android 系統提供的許可權請參閱 Manifest.permission。此外,任何應用都可定義並實施自己的許可權,因此這不是所有可能許可權的詳盡列表

可能在程式執行期間的多個位置實施特定許可權:

  • 在呼叫系統時,防止應用執行某些功能
  • 在啟動 Activity 時,防止應用啟動其他應用的 Activity
  • 在傳送和接收廣播時,控制誰可以接收您的廣播,誰可以向您傳送廣播
  • 在訪問和操作內容提供程式時
  • 繫結至服務或啟動服務

五、自動許可權調整

隨著時間的推移,平臺中可能會加入新的限制
例如,如果應用想要使用特定 API,可能必須請求之前不需要的許可權。因為該應用在之前可以隨意獲取這些 API 應用的訪問許可權,而新的Android系統修改了許可權申請機制,所以 Android 可能會將新的許可權請求應用到應用清單,以免在新平臺版本上中斷應用。
Android 將根據 targetSdkVersion 屬性值決定應用是否需要許可權。如果該值低於在其中新增許可權的版本,則 Android 會自動新增該許可權

例如,API 級別 4 中加入了 WRITE_EXTERNAL_STORAGE 許可權,用以限制訪問共享儲存空間。如果您的 targetSdkVersion 為 3 或更低版本,則會向更新 Android 版本裝置上的應用新增此許可權。

注意:如果某許可權自動新增到應用,則即使您的應用可能實際並不需要這些附加許可權,Google Play 上的應用列表也會列出它們。為避免這種情況,並且刪除不需要的預設許可權,請始終將 targetSdkVersion 更新至最高版本

六、正常許可權和危險許可權

系統許可權分為幾個保護級別,需要了解的兩個最重要保護級別是正常許可權和危險許可權:

  • 正常許可權涵蓋應用需要訪問其沙盒外部資料或資源,但對使用者隱私或其他應用操作風險很小的區域。例如,設定時區的許可權就是正常許可權。如果應用宣告其需要正常許可權,系統會自動向應用授予該許可權
  • 危險許可權涵蓋應用需要涉及使用者隱私資訊的資料或資源,或者可能對使用者儲存的資料或其他應用的操作產生影響的區域。例如,能夠讀取使用者的聯絡人屬於危險許可權。如果應用宣告其需要危險許可權,則使用者必須明確嚮應用授予該許可權

所有危險的 Android 系統許可權都屬於許可權組。如果裝置執行的是 Android 6.0(API 級別 23),並且應用的 targetSdkVersion 是 23 或更高版本,則當使用者請求危險許可權時系統會發生以下行為:

  • 如果應用請求其清單中列出的危險許可權,而應用目前在許可權組中沒有任何許可權,則系統會向使用者顯示一個對話方塊,描述應用要訪問的許可權組。對話方塊不描述該組內的具體許可權。例如,如果應用請求 READ_CONTACTS 許可權,系統對話方塊只說明該應用需要訪問裝置的聯絡資訊。如果使用者批准,系統將嚮應用授予其請求的許可權
  • 如果應用請求其清單中列出的危險許可權,而應用在同一許可權組中已有另一項危險許可權,則系統會立即授予該許可權,而無需與使用者進行任何互動。例如,如果某應用已經請求並且被授予了 READ_CONTACTS 許可權,然後它又請求 WRITE_CONTACTS,系統將立即授予該許可權
  • 任何許可權都可屬於一個許可權組,包括正常許可權和應用定義的許可權。但許可權組僅當許可權危險時才影響使用者體驗。可以忽略正常許可權的許可權組

如果裝置執行的是 Android 5.1(API 級別 22)或更低版本,並且應用的 targetSdkVersion 是 22 或更低版本,則系統會在安裝時要求使用者授予許可權

要想了解更詳細的許可權機制,可以看我的另一篇文章:Android 6.0 執行時許可權解析

七、定義和實施許可權

要實施自己的許可權,必須先使用一個或多個< 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>

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>

需要注意的是,系統不允許多個軟體包使用同一名稱宣告許可權,除非所有軟體包都使用同一證照籤署。如果軟體包宣告瞭許可權,則系統不允許使用者安裝具有相同許可權名稱的其他軟體包,除非這些軟體包使用與第一個軟體包相同的證照籤署。為避免命名衝突,建議對自定義許可權使用相反域名樣式命名

八、自定義許可權建議

應用可以定義自己的自定義許可權,並通過定義 < uses-permission > 元素請求其他應用的自定義許可權。不過,你應該仔細評估您的應用是否有必要這樣做

如果要設計一套向彼此顯示功能的應用,請儘可能將應用設計為每個許可權只定義一次。如果所有應用並非使用同一證照籤署,則必須這樣做。即使所有應用使用同一證照籤署,最佳做法也是每個許可權只定義一次。
如果功能僅適用於使用與提供應用相同的簽名所簽署的應用,你可能可以使用簽名檢查避免定義自定義許可權。當一個應用向另一個應用發出請求時,第二個應用可在遵從該請求之前驗證這兩個應用是否使用同一證照籤署。
如果要開發一套只在您自己的裝置上執行的應用,則應開發並安裝管理該套件中所有應用許可權的軟體包。此軟體包本身無需提供任何服務。它只是宣告所有許可權,然後套件中的其他應用通過 < uses-permission > 元素請求這些許可權。

九、實施 AndroidManifest.xml 中的許可權

可以通過 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


相關文章