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.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 標記允許任何其他軟件包讀取/寫入文件。設置這些標記時,文件仍歸你的應用所有,但其全局讀取和/或寫入權限已適當設置,使任何其他應用都可看見它
##四、使用權限 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)或更高版本,并且應用的 targetSdkVersion 是 23 或更高版本,則應用在運行時向用戶請求權限。用戶可隨時調用權限,因此應用在每次運行時均需檢查自身是否具備所需的權限
- 如果設備運行的是 **Android 5.1(API 級別 22)**或更低版本,并且應用的 targetSdkVersion 是 22 或更低版本,則系統會在用戶安裝應用時要求用戶授予權限。如果將新權限添加到更新的應用版本,系統會在用戶更新應用時要求授予該權限。用戶一旦安裝應用,他們撤銷權限的唯一方式是卸載應用
通常,權限失效會導致 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 callphone numbers without your intervention. Malicious applications maycause unexpected calls on your phone bill. Note that this does notallow 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