1. 程式人生 > >Android API Guides –System Permissions

Android API Guides –System Permissions

系統許可權

宣告:

本文由Gordon翻譯

歡迎轉載,但請保留此宣告

         Android是一個特權分離的作業系統,執行在其上的應用都有一個特定的系統身份(Linux的使用者ID和組ID)。系統的部分也會被分為特定的身份,Linux就是通過這個身份來區別各個應用的。

更加詳細的安全特性是通過“許可權”機制來控制一個程序的特定操作是否可以執行。它通過每一個URI的許可權來來決定他們是否可以訪問特定的資料。

本文將會介紹開發者如何使用Android提供的安全特性。更基礎的文章“Android Security Overview”可以在Android的開源專案中檢視。

安全架構

Android安全架構設計的核心理念就是沒有一個應用可以破壞另外一個應用,作業系統或者使用者。這包括讀寫使用者的私有資料(比如聯絡人和email),讀寫另外一個應用的檔案,進行網路訪問,保持裝置一直醒著或者別的操作。

因為每一個應用都是工作在程序封裝上,所以它必須明確地分享資源和資料。他們可以通過宣告他們需要的許可權來實現資源和資料的共享。應用靜態宣告他們的許可權,然後系統在安裝應用的時候請求使用者同意應用獲得這些許可權。

應用的封裝並不是由編譯應用的技術來決定的,Dalvik虛擬機器(VM)並不是一個特殊安全的界限,每一個應用都可以執行本地的程式碼(參考Android NDK)。每一種型別的應用——Java,本地以及混合的——他們都是用同樣的方式來進行封裝的,並且他們的安全等級也是一樣的。

應用的簽名

所有的APK檔案都必須進行簽名,而且簽名使用的是包含開發者私有金鑰的證書。這個證書指明瞭應用的作者。這個證書並不需要一個證書認證來進行簽名。通常來說,Android的應用使用一個自簽名的證書就足夠了。證書的目的就是為了區分應用的作者。這就使得系統可以判斷應用是否可以訪問簽名級別的許可權,以及是否允許別的應用和這個應用使用同樣的Linux身份(ID)。

使用者ID和檔案存取

在安裝的時候,Android給每個包一個固定的Linux使用者ID,同一裝置上這個ID將會伴隨這個包一生。當然不同裝置上,同一個應用包可能會有不同的使用者ID。不管怎樣在特定的裝置上每個包都有一個特定的UID。

因為安全相關的操作都是在程序級進行執行的,任何兩個應用包的程式碼不能在同一程序執行,因為他們需要在不同的Linux使用者上進行執行。若你想兩個應用使用同樣的使用者ID執行,只要設定AndroidManifest.xml檔案的manifest標籤中的sharedUserId屬性相同即可。這樣做了之後,在安全層面來看,這兩個應用將會被認為是同一個應用,具有相同的使用者ID和檔案許可權。注意,只有兩個使用同樣簽名的應用(當然SharedUserID也得相同)才會給予同樣的使用者ID。

應用儲存的任何資料都應當指定為這個應用的使用者ID,並且不能被別的應用包訪問。當使用getSharedPreferences(String, int), openFileOutput(String, int)或者openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)建立檔案時,你可以使用MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE標誌來允許別的應用對這個檔案的讀寫。當這些標誌被設定之後,這個檔案仍然是屬於你的應用,但是他的全域性讀寫許可權將會被設定,從而讓別的應用可以使用它。

許可權的使用

一個基本的Android應用預設來說是沒有相關許可權的,這也就意味著它不能做任何破壞使用者體驗及裝置資料的操作。為了保護裝置的特性,你必須在AndroidManifest.xml檔案中宣告一個或者多個<uses-permission>標籤。

例如,一個需要監聽SMS資訊的應用應當設定如下:

1
2
3
4
5
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>

在應用安裝的時候,應用所請求的許可權(使用者的簽名和宣告時確定的)會在包安裝的時候讓使用者賦予給應用。應用在執行的時候,就不會再讓使用者進行檢查了,應用要不在安裝的時候被賦予特定的許可權,從而可以使用相應的特性,要不就沒有使用相關特性的許可權。

通常情況下,若是許可權失敗將會導致一個SecurityException傳送給應用,但是並不是在任何地方都會產生這個exception。舉例來說,sendBroadcast(Intent)方法因為要把資料傳送給每一個receiver,在這個方法返回的時候它會檢查他們的許可權,但是若是有許可權的失敗,你不會收到exception。當然,幾乎所有的許可權失敗的情況,都會被列印到系統log中。

然而,正常使用者使用的情況下(比如應用從Google應用商店裡安裝),若是使用者不允許應用申請的許可權,這個應用就不會被安裝成功。所以,一般來說你沒有必要擔心執行時缺少許可權,因為事實上你的應用在安裝的時候就已經獲得了它想要的相應許可權。

Android系統提供的許可權可以參考Manifest.permission檔案。任何應用也都有可能定義並支援它自己的許可權,所以這個列表也不能包含所有的可能許可權。

  • 一個特定的許可權可能在你的應用操作的一系列地方被實施:
  • 當呼叫到系統的時候,為了防止執行特定的函式。
  • 當開始一個activity的時候,為了防止一個應用呼叫另外一個應用的activity。
  • 在傳送和接收broadcast,在控制誰能接收你的broadcast或者誰能傳送broadcast給你。
  • 繫結或者啟動service。

注意:

隨著時間的推移,平臺可能會增加新的許可權要求,所以為了使用特定的API,你的應用需要請求一些之前不用請求的許可權。因為已經存在的應用可能認為訪問這些API是直接可用的,Android可以在應用的manifest檔案中直接申請新的許可權請求從而避免在新的平臺版本上破壞原有的應用。Android作出應用可能需要許可權的描述是基於targetSdkVersion屬性的。假如這個的值小於許可權假如的版本,那麼Android就加入對應的許可權。

舉例來說,WRITE_EXTERNAL_STORAGE許可權是在API級別4中加入的,它是為了防止訪問共享的儲存空間。假如你的targetSdkVersion小於3,那麼在新的Android版本中會把這個的許可權加入到你的應用中。

需要注意的是,假如這種情況在你的應用中發生,即使這些許可權在你的應用中可能沒有真正地請求,Google應用商店也會在顯示你的應用的時候請求這些許可權。

為了避免這種情況發生,你需要及時把你的targetSdkVersion更新到足夠高的版本。你可以參考Build.VERSION_CODES文件來看每次釋出都加入了哪些許可權。

宣告和實施許可

為了實施你的許可權,你需要首先在AndroidManifest.xml檔案中使用<permission>標籤來宣告他們。

例如,一個應用想要控制誰可以啟動它的activity,就應當為這個操作宣告一個許可權,具體的方法如下:

1
2
3
4
5
6
7
8
9
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.me.app.myapp" >
<permission android:name="com.me.app.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>屬性是需要的,它是用來告訴系統應用需要這個許可權的時候是如何來通知使用者的,或者誰是被允許得到這個許可權的,具體的描述可以參考連結的檔案。(譯者注:後期翻譯ok會加入)。

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

你可以通過設定應用和shell命令“adb shell pm list permissions”來檢視當前系統定義的許可權。若是使用設定應用,可以在設定->應用下面進行檢視。選取一個使用者,向下滾動,可以看到應用使用的許可權。對開發者來說,adb –s的選項將會以使用者看到的形式來顯示對應的許可權:

1
2
3
4
5
6
7
8
9
10
11
12
$ 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
 
...

在AndroidManifest.xml中執行許可權

高級別的許可權會限制訪問系統的整個元件,應用可以通過AndroidManifest.xml檔案獲得。所有的這些請求都包含在相關元件的android:permission屬性中,它會命名控制訪問的許可權名。

Activity許可權(<activity>標籤)限制了誰可以訪問對應的activity。這個許可權是通過Context.startActivity()和Activity.startActivityForResult()進行檢查的。假如呼叫者沒有這個許可權,那麼就會產生一個SecurityException。

Service許可權(<service>標籤)限制了誰可以啟動和繫結對應的service。這個許可權是通過Context.startService(),Context.stopService()以及Context.bindService()來檢查的。假如呼叫者沒有這個許可權,同樣會產生一個SecurityException。

BroadcastReceiver許可權(<receiver>標籤)限制了誰能向相關的receiver傳送廣播。這個許可權是在Context.sendBroadcast()之後檢查許可權的,因為這個時候系統會把廣播發送給特定的接受者。這樣一來,許可權的失敗將不會產生一個exception返回給呼叫者,他只會不傳送intent而已。同樣的,許可權可以通過Context.registerReceiver()來控制誰能夠廣播給一個通過程式註冊的receiver。另一方面,當呼叫context.sendBoradcast()的時候也會提供許可權來限制哪一個BoradcastReceiver物件可以接收這個廣播。(詳情見下面)

ContentProvider許可權(<provider>標籤)限制了誰可以訪問ContentProvider的資料。(Content provider還有一個重要的額外可用的安全機制稱之為URI許可權,這個會在稍後進行介紹)。和別的元件不同,這裡有兩個獨立的許可權屬性可以設定:android:readPermission限制了誰可以讀這個provider,android:writePermission限制了誰可以寫它。注意,provider是由讀寫許可權分別保護的,獲得些許可權不意味著你可以讀。這個許可權是在你第一次檢索provider進行檢查的(假如你沒有許可權,一個securityException將會丟擲),當然你在這個provider上進行操作的時候也會檢查相應的許可權。可以使用ContentResolver.query()函式來請求得到讀的許可權,使用ContentResolver.insert(),ContentResolver.update(), ContentResolver.delete()來請求寫的許可權。在所有這些情況中,若是沒有得到相應許可權都會丟擲一個SecurityException。

當傳送廣播的時候執行許可權

除了上文提到的對於註冊過的BroadcastReceiver傳送intent的時候執行許可權之外,你還可以在傳送廣播的時候請求許可權。在呼叫Context.sendBroadcast()的時候,加入一個許可權字串,你就可以要求這個接收的應用必須有響應的許可權才能接收廣播。

注意,傳送者和接收者都可以請求許可權。這種情況下,在傳送intent和接收端都需要進行相應的許可權檢查。

其他的許可權執行

更細的許可權會在呼叫service時執行。這個是通過Contex.checkCallingPermission()方法來實現的。在呼叫這個方法的時候傳入一個許可權字串,就會返回一個整形值用來表示當前的程序是否已經獲得了對應的許可權。注意,這個只會在別的程序執行一個呼叫才會被使用,通常來說是通過一個service的IDL介面或者提供給別的程序的其他方法來實現。

還有一些別的方式來檢查許可權。假如你有另外程序的pid,你可以使用Context.checkPermission(String, int, int)來檢查對應的許可權。假如你有別的應用的包名字,你可以使用PackageManager.checkPermission(String, String)來檢查對應的包是否獲得對應的許可權。

URI許可權

到目前為止所描述的標準的許可權系統對content providers的使用來說都不是很好。content provider可能想要保護它自己的讀寫許可權,尤其是當它的直接使用者對別的應用操作獲取特定的URI的時候。一個典型的例子就是郵件應用中的附件。郵件的訪問需要許可權進行設定,因為畢竟使用者的資料是敏感的。然而,假如圖片瀏覽器獲得了一個圖片附件的URI,這個圖片瀏覽器將不會有開啟圖片的許可權,因為它沒有道理去獲得訪問郵件的許可權。

這個問題的解決方法就是每一個URI的許可權:當啟動一個activity或者返回一個結果給activity的時候,呼叫者可以設定Intent.FLAG_GRANT_READ_URI_PERMISSION或者Intent.FLAG_GRANT_WRITE_URI_PERMISSION。這個允許接收activity獲得訪問intent中特殊資料URI的許可權,而不用管他是否有許可權訪問這個Intent提供者的資料許可權。

這個機制允許了一個使用者互動時(開啟一個附件,選擇一個聯絡人等)對專用的更細的許可權訪問的通用模型。這將會是減少應用需要許可權的一個關鍵所在,應用只需要那些他們特性直接相關的許可權即可。

然而對細粒度URI許可權的獲取,需要這些URI的content provider也要做一些相應的操作。我們推薦這些content provider實現這個功能,他們只需要通過android:grantUriPermissions屬性或者<grant-uri-permissions>標籤來宣告即可。

更多的資訊請參考Context.grantUriPermission(),Context.revokeUriPermission()和Context.checkUriPermission()方法。

推薦您繼續閱讀以下內容:

Permission that Imply Feature Requirements

關於如何通過請求許可權來限制你的應用只在包含對應硬體或者軟體特性的裝置上執行。

<uses-permission>

Manifest標籤下的API參考,他聲明瞭應用需要的系統許可權。

Manifest.permission

所有系統許可權的API參考。

以下內容你可能也感興趣:

關於Android在不同型別裝置上執行的資訊,他會介紹如何針對不同裝置進行優化你的應用以以及如何在不同裝置上執行你的應用。

Android Security Overview

更詳細的關於Android平臺的安全模式。