Android開發指南之系統權限

Android 是一個特權分離(privilege-separated)操作系統,在其中每個應用都以一個不同的系統身份運行(Linux用戶ID或者組ID)。系統的各個部分也被分成不同的系統身份。因此,Linux才能把應用與應用以及應用與系統相互隔離開。

通過一個細粒度的、提供額外安全特性的 “permission” 機制來加強對特定的操作的限制以至于某些特定進程才能執行。以及通過 “per-URI ”權限機制來分配對某些數據的臨時訪問。

本文檔描述了應用開發者該怎樣使用Android提供的安全特性。Android開源項目(AOSP,Android Open Source Project)提供了一個更綜合的Android安全概覽

安全架構


Android安全架構的一個中心設計宗旨是默認情況下任何應用都沒有權限執行對其他應用、操作系統和用戶產生不利影響的操作。這包括讀寫用戶的私有數據(例如聯系人或者郵件)、讀寫其他應用的文件、連接網絡、保持設備喚醒狀態等等

由于每個Android應用都運行在一個線程沙盒中,因此應用必須明確地共享資源和數據。為了這個目的,它們需通過聲明權限來獲取基本沙盒無法提供的額外的能力。應用靜態地聲明他們所需的權限,在運行時,Android 系統提示用戶,以使應用獲得準許。

應用沙盒并不依賴構建應用程序的技術。安全系統并不僅限于Dalvik VM,任何應用都能運行native代碼(見Android NDK)。所有類型的應用--Java的,native的以及混合的--都是在沙盒中運行的,以同樣的方式、并且互相之間有同樣的安全級別。

應用簽名


所有的APk(.apk文件)必須使用證書簽名,證書的私鑰由開發者持有。這個證書用來辨別應用的所有者。證書不必由證書機構來簽名,一個完全正當的、也是典型的做法是Android應用使用開發者自己簽名的證書。在Android中,證書的作用就是區分應用的所有者。這使得系統能授權或拒絕應用對簽名級別權限的獲取,以及相同開發者的另外的應用請求被分配相同的Linux標識

用戶 ID 和文件訪問


在安裝時,Android 給每個 package 一個獨特的 Linux 用戶 ID 。在某臺設備上這個 package 的生命周期里,這個身份一直是不變的。相同的 package 在不同的設備上可能會有一個不同的 UID ;真正重要的是,在同一個設備上不同的 package 是有不同的 UID 。

由于安全增強發生在線程級別,兩個 package 的代碼不能在一個相同的線程正常運行,因為他們必須以不同的 Linux 用戶運行。你可以使用AndroidManifest.xmlmanifext 標簽的 sharedUserId 屬性來使不同應用被分配相同的用戶 ID 。通過這樣,出于安全目的,兩個package就被當作同一個應用程序,擁有相同的用戶 ID 和文件權限。出于安全目的,只有當兩個應用使用相同簽名(和擁有相同的sharedUserId)才會被分配相同的用戶 ID 。

任何由某個應用存儲的文件的所有者都會被指定為該應用的用戶 ID ,其他應用都不能正常的訪問到。當通過
getSharedPreferences(String, int) , openFileOutput(String, int)openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)來創建文件時,你可以使用MODE_WORLD_READABLEMODE_WORLD_WRITEABLE標志來允許其它應用讀/寫文件。設置這些標志之后,這些文件仍然由你的應用所擁有,但它的全局讀/寫權限已經被正確地設置了,所以其他應用也就能看見它了。

使用權限


默認情況下,一個基礎的Android應用不擁有任何權限,這意味著它不會對用戶體驗和設備上數據產生不利影響。要使用設備上那些受保護的功能,你應用的manifest必須包含一個或多個 <uses-permission> 標簽
例如,一個需要監控接收的SMS消息的應用就需指定:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    ackage="com.android.app.myapp" >
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>

如果你的應用僅包含 normal 權限(即不會對用戶的隱私或者設備的運行產生風險的權限),系統會自動授權這些權限。但是如果你包含 dangerous 權限(即會對用戶的隱私或者設備的正常運行產生潛在影響的權限),系統就會詢問用戶,以便精確的授予這些權限。系統請求這些權限的方式視系統版本和你的應用的目標系統版本而定。

  • 如果設備運行的是Android 6.0(API level 23)或更高,并且應用的targetSdkVersion是23及以上,應用是在運行的時候向用戶請求權限。用戶可以在任何時候取消這些權限,所以應用必須每次在運行的時候都去檢查它是否有這些權限。更多關于請求權限的信息,您可以參閱處理系統權限指南。
  • 如果設備運行的是Android5.1(API level 22)或以下,或者應用的targetSdkVersion是22及以下,系統是在用戶安裝應用的時候向用戶申請授權這些權限的。如果你在應用的新版本中添加了一個新權限,系統會在用戶升級應用版本的時候向用戶申請授權這個權限。一旦用戶安裝了這個應用,唯一的取消權限的方法是卸載這個應用。

通常,權限申請失敗會導致系統將SecurityException拋回給應用。然而,系統并不保證在任何地方都是這樣。例如,sendBroadcast(Intent)方法僅在數據被分發到各個 receiver 的時候才去檢查權限,這發生在方法被調用并返回之后。所以如果權限申請失敗,你不會接收到任何異常。但是在幾乎所有情形中,權限申請失敗都會被打印至系統日志。

Android系統提供的權限都能在Manifest.permission中找到。任何應用都能定義或增強它自己的權限,所以它沒有列舉所有可能的權限。
在你的程序運行期間,在很多地方權限都能被加強。

  • 在調用系統功能時,阻止應用執行特定的功能。
  • 在啟動 activity 時,阻止應用啟動其它應用的 activity 。
  • 在發送和接收廣播時,控制誰能接收你的廣播或者誰能發送廣播給你 。
  • 在訪問或操作 content provider 的時候
  • 綁定或者啟動一個service的時候

權限級別
更多關于權限的不同保護基本,見正常和危險權限

自動調整權限

隨著時間的流逝,系統平臺添加了新的限制,以至于為了使用某些API,你的應用必須申請那些以前不需要申請的權限。那些已經存在的應用假設這些API仍然是可隨意調用的,為了避免舊應用在新平臺的崩潰,Android會在這些應用的manifest中添加這些權限。Android會基于應用的targetSdkVersion屬性提供的值來決定應用是否需要這些權限。如果這個值低于這些權限被添加進去時的版本,Android就會增加這些權限。

例如,WRITE_EXTERNAL_STORAGE 權限是在API level 4時添加的,目的是限制對共享存儲空間的訪問。如果你的targetSdkVersion是3或者更低,在新版本的Android中,這個權限就會自動被添加至你的應用。

注意:如果一個權限被自動添加至你的應用,在Google Play中你的應用仍會列舉這些額外的權限,即使你沒有請求他們。

為了避免這種情況發生以及移除這些你不需要的默認的權限,請盡可能提高你的targetSdkVersion。你可以在Build.VERSION_CODES中查看每次發布都添加了哪些權限。

普通的和危險的權限


系統權限分成了幾個保護級別。最重要兩個保護級別就是 normaldangerous 權限。

  • Normal 權限包括了你的 app 需要訪問 app 的沙盒之外數據或者資源的操作,但這些操作僅會對用戶的隱私或其它app的運行造成極小影響。例如,設置時區權限是一個普通權限,如果一個應用聲明了這個普通權限,系統會自動分配這些權限給應用。查看完整的正常權限列表,請參閱正常權限
  • Dangerous 權限包括了那些app需要的數據和資源涉及到用戶的私有信息,以及那些可能會影響用戶存儲的數據或其它應用的運行。例如,讀取用戶聯系人的能力就是一個危險權限。如果一個應用聲明它需要一個危險權限,用戶分配權限給應用時,必須明確地知道他在做什么。

特殊權限
有兩個權限既不像普通權限也不像危險權限。SYSTEM_ALERT_WINDOWWRITE_SETTINGS尤其敏感,所以大多數應用不應使用他們。如果某一個應用需要這些權限,它必須在manifest中聲明這個權限,并且發送Intent 以請求用戶的授權。系統展示一個詳細的管理屏幕屏幕給用戶以響應這個Intent。更多關于怎么請求這些權限的細節,請參閱SYSTEM_ALERT_WINDOWWRITE_SETTINGS詞條。

權限組

所有的危險系統權限都歸屬于一個權限組。如果設備運行Android 6.0(API level 23)并且app的targetSdkVersion是23及以上,當你的應用申請一個危險權限的時候。系統的會響應如下:

  • 當應用請求manifest中的一個危險權限,且應用當前沒有擁有(與請求的權限在同一個)權限組中的其他權限時,系統會顯示一個對話框給用戶,描述應用想訪問的權限組。但對話框并不會描述那個權限組中的哪個具體權限。例如,如果app申請READ_CONTACTS權限,系統對話框之后說應用需要訪問設備的聯系人。如果通過授權,系統只會給應用分配它請求的那個權限。
  • 如果應用請求manifest中的一個危險權限,且應用已經有另一個在相同權限組的危險權限了,系統會立即分配權限給它,不會與用戶有任何的交互。例如,如果一個應用已經請求且被授予READ_CONTACTS權限了,然后它又申請WRITE_CONTACTS 權限,系統會立即分配這個權限。

任何權限都屬于一個權限組,包括普通權限和你app自己定義的權限。然而,只有當權限是危險權限的時候權限組才會影響用戶體驗。你可以忽略普通權限的權限組。

如果設備運行的是Android 5.1(API level 22)或更低,或者app的targetSdkVersion是22及以下,系統會在安裝的時候請求用戶授予這些權限。再次強調,系統僅會告訴用戶app需要的權限組,而不是具體的某個權限。

權限組 權限
CALENDAR READ_CALENDAR WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS WRITE_CONTACTS GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION ACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE CALL_PHONE READ_CALL_LOG WRITE_CALL_LOG ADD_VOICEMAIL USE_SIP PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS RECEIVE_SMS READ_SMS RECEIVER_WAP_PUSH RECEIVE_MMS
STORAGE READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE

定義和增強權限

為了增強你自己的權限,你必須首先在你的 AndroidManifest.xml 中聲明他們,通過使用一個或多個 <permission> 標簽。

舉個例子,一個應用想控制誰能啟動它的activity,可以參照以下方法聲明一個權限:

<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> 屬性是必選項。正如鏈接的文檔中所描述的,在應用請求這個權限的時候,它告訴系統該怎樣通知用戶,或者誰被準許持有這個權限。

<permissionGroup>屬性是可選項,只在系統給用戶展示權限時有用。你可以設置它為一個標準的系統分組(列舉在android.Manifest.permission_group中)或者一個你自己定義的分組。最好是使用一個已經存在的分組,因為這會簡化展示給用戶的權限UI。

需注意到的是,在聲明 permission 時,label 和 description 都是必選項。這兩個字符串資源屬性被用于在用戶瀏覽權限列表(android:label)和權限細節(android:description)的時候展示。label必須短小精悍,用幾個簡單的字概括被保護的權限的關鍵信息。description 必須用幾句話就描述允許權限擁有者能做什么。通常是用兩句話概括。第一句描述權限,第二句警告用戶如果授權后,會發生哪些糟糕的事情。
下面是 CALL_PHONE 權限的 label 和 的 description 的范例:

    <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 permission查看。在設置應用中可通過Settings>Applications查看。選擇一個app,向下滑動查看這個 app 使用的權限。對開發者來說,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

在 AndroidManifest.xml 中增強權限

在你的 AndroidManifest.xml 中,可以通過權限嚴格限制對系統或應用的所有組件的訪問。僅需添加一個android:permission屬性在那個組件上,即意味著訪問它的權限就應用上了。

Activity權限(應用在<activity>標簽上)限制誰能啟動這個activity。在執行Context.startActivity()Context.startActivityForResult()的時候檢查權限。如果調用者沒有相應的權限,那就會在這個調用上拋出一個SecurityException異常。
Service權限(應用在<service>標簽上)限制誰能啟動或綁定這個service。在執行Context.startService()Context.startService()Context.stopService()的時候檢查權限。如果調用者沒有相應的權限,那就會在這個調用上拋出一個SecurityException異常

BroadcastReceiver權限(應用在<receiver>標簽上)限制了誰能發送廣播到這個receiver上。系統是在Context.sendBroadcast()方法返回后執行的權限檢查,當系統試圖傳遞這個被提交的廣播給指定的receiver的時候。因此,異常不會拋回給調用者,只是不會傳遞Intent而已。同樣地,我們可以在調用Context.registerReceiver()時指定一個權限來控制誰能發送廣播給一個動態注冊的receiver。此外,在調用Context.sendBroadcast()方法的時候,我們可以給其添加一個權限來限制哪個BroadcastReceiver對象能接收這個廣播(見后面的內容)。

contentProvider權限(應用在<provider>上)限制誰能訪問在ContentProvider上的數據(稍后我們會看到Content Provider還有一個重要的額外的安全措施,即URI Permission)。不像其它的組件,Content Provider有兩個獨立的權限屬性:android:readPermission限制誰能從 provider 讀取數據;android:writePermission限制誰能往里面寫數據。要注意的是,如果一個 provider 同時被讀權限和寫權限保護,只擁有寫權限并不意味著你能從 provider 里面讀數據。在你第一次從provider中讀取數據(如果你沒有任何權限,將會拋出一個SecurityException),以及你在provider上執行操作的時候,系統執行權限檢查。使用ContentResolver.query()要求讀權限。使用ContentResolver.update()ContentProvider.delete() 以及 ContentProvider.insert() 要求寫權限。在以上所有的情形中,沒有所需要的權限都會在執行時導致SecurityException異常的拋出。

發送廣播的時候增強權限

如前文所述的增強誰能發送 Intent 給注冊的 BroadcastReceiver的權限之外,你還可在發送廣播的時候指定一個權限。調用一個添加了權限字符串Context.sendBroadcast(),則要求廣播接收器所屬的應用必須有那個權限才能接收你的廣播。由于接收器和廣播器都需要權限,在這種情況下,兩個權限檢查都必須通過了才能將Intent 發送到相應的目標。

其它

任何細粒度的權限能在service的任意地方被檢測(Arbitrarily fine-grained permissions can be enforced at any call into a service)。這由 Context.checkCallingPermission() 方法做到。在執行時傳遞一個所需要的權限字符串,它就會返回一個標識當前調用線程是否有這個權限的整數。要注意的是,只有你在其他線程執行調用的時候這個才能使用,通常是通過由一個service發布的IDL接口或者其他在另一個線程中的方法。
有很多有用方法可以用來檢查權限。如果你有另外線程的pid,你可以使用 Context 的 Context.checkPermission(String,int,int) 來檢查那個線程的權限。如果你有其他應用的包名, 你可以直接調用 PackageManage 的方法 `PackageManager.checkPermission(String,String)來確定那個package是否已獲得某個權限。

URI 權限


迄今為止,標準的權限系統在content provider 權限方面還不是很有效。一個content provider 可能想用讀寫權限來保護自己,并且它的客戶端也需要傳遞那些URI給其他應用以便他們能操作。典型的應用是郵件應用中的附件。對郵件的訪問需要由權限來保護,因此它是敏感的用戶數據。然而,如果一個圖片附件的URI被傳遞給了圖片查看器,圖片查看器不能擁有打開這個附件的權限,因為它沒有理由有所有電子郵件的權限。

這個問題的解決方案是 per-URI 權限:當啟動一個activity 或返回一個結果給 activity , 調用者可以設置 Intent.FLAG_GRANT_READ_URI_PERMISSIONIntent.FLAG_GRANT_WRITE_URI_PERMISSION。在Intent 中給接收 activity 分配了數據 URI 的權限,不論是否其擁有 content provider 中數據的權限。

這個機制允許一個通用的授權模型,在這種模型下用戶交互(打開附件,從列表中選擇一個聯系人等等)使用臨時的細粒度的權限. 這是一個重要措施,減少了應用所需的和那些與行為直接關聯的權限。

分配細粒度URI權限要求與擁有這些權限的content provider的合作。強烈建議 content provider 應用這些措施,并且,通過android:grantUriPermissions 屬性或 <grant-uri-permissions>標簽聲明支持這種特性。

查閱更多信息,參看
[Context.grantUriPermission()](http://developer.android.com/reference/android/content/Context.html#grantUriPermission(java.lang.String, android.net.Uri, int)),Context.revokeUriPermission(http://developer.android.com/reference/android/content/Context.html#revokeUriPermission(android.net.Uri, int)),[Context.checkUriPermission()](http://developer.android.com/reference/android/content/Context.html#checkUriPermission(android.net.Uri, int, int, int)),

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,268評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 此刻 她躺在我的懷里 睡著了 安詳像只憩身在地洞里的兔子 她一定夢到了什么 要把腿用力的揉進我的身體 又或者根本沒...
    光皂閱讀 278評論 0 0
  • “李羊,李羊,你快看那個五彩斑斕的地方是賣什么的?是不是一家糖果店啊?!” 上一章節 第八章 “我們臨走的那天,爸...
    火把山閱讀 204評論 2 6
  • 【易經原文·坤卦二爻爻辭及象傳】 [爻辭]六二:直、方、大(1)。不習無不利(2)。[象傳]象曰:六二之動,直以方...
    大珊老師閱讀 1,339評論 1 3