第6章 許可權(Permissions)
本文檔介紹了應用程式開發人員如何使用由Android提供的安全功能。在Android 開放原始碼項目AOSP(Android Open Source Project)中提供了更普通的Android安全性概述。Android是一種分許可權的作業系統,在Android上啟動並執行每個應用程式都具有各自獨立的系統標識(Linux使用者ID和組ID)。系統各部分有不同並明顯的標識。因此,Linux上啟動並執行各個應用程式相互獨立且與系統無關。
Android的“permissions”機制通俗來說就是你程式就算實現了那個功能,如果沒申請許可權的話,那個功能一樣運行部了。並且每個應用程式的許可權都是獨立的。
6.1 安全性架構
Android安全體系架構設計的核心是在預設情況下不允許任何一個程式可以執行對其他程式、作業系統或者使用者有害的操作,包括讀寫使用者的隱私資料(例如連絡人或者電子郵件),讀寫其他程式的檔案,進行網路訪問或者喚醒裝置等等。由於Android系統讓每個應用程式運行在獨立的沙箱(sandboxes)中,應用程式必須通過聲明所需要的許可權來明確的分配資源和資料。Android沒有使用不利於安全的動態授權機制。應用程式靜態聲明他們所需要的許可權,在程式安裝時Android系統會提示使用者是否同意它們擷取這些許可權。不同意的話就不要安裝,安裝了即同意。Dalvik虛擬機器不是一個安全的邊界,任何的應用程式都能夠運行本地代碼(參照Android NDK)。所有類型的應用程式——java、native和兩者混合的——均用相同方式和相同程度的安全等級在沙箱中運行。
6.2 應用程式數位簽章
所有的Android應用程式(apk檔案)都必須讓某個開發人員持有私密金鑰、用於識別應用程式作者的認證進行簽名。該認證要求很寬鬆,並不需要由專門的憑證授權單位進行簽名,Android應用程式可以使用自簽名的認證。Android認證的目的是區分應用程式的作者,可以允許作業系統授予或者拒絕應用程式使用簽名層級的許可權和作業系統授予或者拒絕應用程式請求和其他應用程式相同的Linux身份。關於這一點,在調試階段的時候,如果你有在兩台電腦分別編譯器,產生2個相同APK後,裝入手機的話,後面那個會提示“安裝未完成”或非法之類。這是由於你兩台電腦的調式模式下的簽名不一樣,在正式發布的時候,使用Eclipise可以給程式正式簽名,在不同的電腦上發布只要使用相同的正式簽名即可。當然調試階段不用這麼麻煩。
6.3 使用者ID和檔案訪問
在安裝的時候,Android會給每個程式分配一個不同的Linux使用者IU(UID)。軟體在裝置上的生命週期中這個身份標識保持不變。在不同的裝置上,相同的軟體可能會有一個不同的UID;重要的是在給定的裝置上不同的包是不同的UID。因為安全是在進程層級上實現的,理論上,兩個軟體包的代碼在同一個進程中不能夠同時正常運行,他們必須以不同的Linux使用者運行。實際上,可以在每個程式的AndroidManifest.xml中將manifest標籤的shareUserId屬性分配相同的使用者ID,把兩個應用程式看作擁有同樣的使用者ID和檔案許可權的同一個應用程式。為了保持安全,只有具有相同的數位簽章(請求的sharedUserId也相同)的應用程式才會分配相同的使用者ID。任何由應用程式儲存的資料將被賦予應用程式的使用者ID,正常情況不能被其它應用程式訪問。當使用getSharedPreferences(String, int),openFileOutput(String,int),或openOrCreateDatabase(String,int, SQLiteDatabase.CursorFactory)建立一個新的檔案時,可以使用MODE_WORLD_READABLE或MODE_WORLD_WRITEABLE標記允許其他應用程式來讀/寫檔案。設定這些全域的讀寫權限後,該檔案仍然由建立改檔案的應用程式所擁有,但任何其他應用程式可以看到讀寫它。
6.4 使用許可權
一個Android應用程式沒有任何許可權,這意味著它不能做任何會對使用者體驗或裝置上的任何資料造成不利影響的操作。要利用裝置的保護功能,必須在AndroidManifest.xml檔案中的一個或多個<uses-permission>標籤上聲明應用程式所需要的許可權。例如,一個監控接收簡訊的應用程式需要聲明如下許可權,如代碼清單6-1所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.app.myapp" > <uses-permission android:name="android.permission.RECEIVE_SMS" /> ...</manifest>
代碼清單 6-1
在安裝應用程式時,安裝者要基於應用程式的簽名在互動時對應用程式所需的許可權進行授權。應用程式運行時不會進行許可權檢查:它要麼在安裝後被給予一個特殊的許可權,並且可以使用它期望的許可權,要麼就不被授予許可權,任何使用這些許可權的操作都會在沒有使用者提示的情況下自動失敗。通常如果請求許可權失敗應用程式會拋出一個SecurityException異常,但是也有特例。例如,sendBroadcast(Intent)函數在所有資料被投遞到接收者時檢查許可權,當函數返回後,不會對資料的許可權進行檢查,也不能接收到任何許可權異常。約大多數情況下,許可權異常會記錄在日誌中。所有Android系統提供的許可權可以在Manifest.permission中找到。任何應用程式也可以定義並執行其自己的許可權,
6.5 聲明和實施許可權
為了執行你自己的許可權,你必須首先在你的AndroidManifest.xml中使用一個或多個<permission>標籤聲明它們。例如,一個應用程式想要控制誰能夠啟動它的一個Activity,可以這樣聲明,如代碼清單6-2所示:
<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>
代碼清單 6-2
<protectionLevel>屬性是必選的,它告訴系統當其它應用程式需要該許可權時怎樣通知使用者或者誰可以使用該許可權。<permissionGroup>屬性是可選的,用於協助系統顯示許可權給使用者。通常會將它設定為在android.Manifest.permission_group中的一個標準的系統組,在極少數情況下也可以由自己進行定義。最好是優先使用現有的組,便於簡化許可權UI展示給使用者。請注意,這兩個標籤和描述應提供許可。使用者在查看的許可權列表(android:label)或單個許可權( android:description)的細節時,這些內容會被顯示。標籤應該簡潔的介紹許可權保護的關鍵功能。用幾個簡單的句子描述擁有該許可權可以做什麼。慣例是用兩個句子,第一句描述許可權,第二句警告使用者當授權該許可權後會發生什麼。
你也可以通過shell命令 adb shell pm list permissions來查看現在系統上的許可權定義。特別地,-s選項可以用簡單的表格形式來顯示。如代碼清單6-3所示:
$ adb shell pm list permissions -sAll Permissions: Network communication: view Wi-Fi state, create Bluetooth connections, fullInternet 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 ...
代碼清單 6-3
1. 在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>標籤)用於限制誰可以向相關的接收器發送廣播。許可權檢查會在Context.sendBroadcast()返回後當系統去發送已經提交的廣播給相應的Receiver時進行。最終,一個permission failure不會再返回給調用方一個異常,只是不會去實現該Intent而已。同樣地,Context.registerReceiver()也可以通過自己的permission用於限制誰可以向一個在程式中註冊的receiver發送廣播。另一種方式是,一個permission也可以提供給Context.sendBroadcast() 用以限制哪一個BroadcastReceiver才可以接收該廣播。
◆ContentProvider許可權(應用於<provider>標籤)用於限制誰才可以訪問ContentProvider提供的資料。(Content providers有一套額外的安全機制叫做URI permissions,這些在稍後討論。)不像其他組件,它有兩個單獨的許可權屬性,你可以設定:android:readPermission用於限制誰能夠讀,android:writePermission用於限制誰能夠寫。需要注意的是如果provider同時需要讀寫許可,只有寫許可的情況下並不能讀取provider中的資料。當你第一次檢索Content Provider和當完成相關操作時會進行許可權檢查。(假如沒有任何許可權則會拋出SecurityException異常。)使用ContentResolver.query()需要持有讀許可權;使用ContentResolver.insert(),ContentResolver.update(),ContentResolver.delete()需要寫入權限。在所有這些情況下,沒有所需的許可權將會導致拋出SecurityException異常。
2. 發送廣播執行許可權
除了之前說過的許可權(用於限制誰可以發送廣播給相應的BroadcastReceiver),還可以在發送廣播的時候指定一個許可權。在調用Context.sendBroadcast()的時候使用一個許可權字串(permission string),你就可以要求接收的宿主程式必須有相應的許可權。值得注意的是接收者和廣播都可以要求許可權。當這種情況發生時,這兩種許可權檢查都需要通過後才會將相應的intent發送給相關的目標。
3. 執行其他許可權
在調用service的過程中可以設定更加細化的許可權。這是通過Context.checkCallingPermission()方法來完成的。調用的時候使用一個想得到的許可權字串(permission string),返回給調用方一個整數判斷是否具有相關許可權。需要注意的是這種情況只能發生在來自另一個進程的調用,通常是一個service發布的IDL介面或者是其他方式提供給其他的進程。Android提供了很多其他有效方法用於檢查許可權。如果有另一個進程的pid,可以通過Context.checkPermission(String, int, int)去檢查該進程的使用權限設定。如果有另一個應用程式的包名,可以直接用PackageManager.checkPermission(String, String)來確定該包是否已經擁有了相應的許可權。
6.6 URI許可權
到目前為止我們討論的標準的許可權系統對於content provider來說是不夠的。一個Content Provider可能想保護它的讀寫權限,而同時與它對應的用戶端也需要將特定的URI傳遞給其它應用程式以便對該URI進行操作。一個典型的例子是郵件應用程式的附件。訪問郵件需要使用permission來保護,因為這些是敏感的使用者資料。然而,如果有一個指向圖片附件的URI需要傳遞給圖片瀏覽器,那個圖片瀏覽器是不會有訪問附件的權利的,因為它不可能擁有所有的郵件的存取權限。針對這個問題的解決方案就是per-URI 許可權:當啟動一個activity或者給一個activity返回結果的時候,調用方可以設定Intent.FLAG_GRANT_READ_URI_PERMISSION和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。這授予接收activity訪問該Intent指定的URI的許可權,而不管它是否有許可權進入該Intent對應的content provider。這種機制允許一個通常的(capability-style)模型,以讓使用者互動(如開啟一個附件, 從列表中選擇一個連絡人)並細化的許可權。這是實現減少應用程式所需要的許可權而只留下和程式行為直接相關的許可權時很關鍵的一步。強烈建議在content provider中實現這種功能,並通過android:grantUriPermissions或者<grant-uri-permissions>標籤來聲明支援。
FAQ群213821767