一、Android的權限機制
Android是目前最流行的智慧型手機軟體平臺之一,在智能移動終端如火如荼發展的同時,其安全態勢也日益嚴峻。有調查表明,惡意軟體的數量在持續的上升,Google在Android安全機制上面也做了很多工作,並且一直在持續的更新,其Android的安全模型由3個部分組成:Linux安全機制、Android本地庫及運行環境安全與Android特有的安全機制,如下圖:
本文只涉及到其中的權限機制介紹,其他的部分如果有感興趣的,我們可以後續一起探討。
Android的權限管理遵循的是「最小特權原則」,即所有的Android應用程式都被賦予了最小權限。一個Android應用程式如果沒有聲明任何權限,就沒有任何特權。因此,應用程式如果想訪問其他文件、數據和資源就必須在AndroidManifest.xml文件中進行聲明,以所聲明的權限去訪問這些資源。否則,如果缺少必要的權限,由於沙箱的保護,這些應用程式將不能夠正常提供所期望的功能與服務。
所有應用程式對權限的申請和聲明都被強制標識於AndroidManifest.xml文件之中,通過<permission>,<permission-group>,<permission-tree>等標籤指定。如果需要申請某個權限,可以通過<uses-permission>指定。應用程式申請的權限在安裝時提示給用戶,用戶可以根據自身需求和隱私保護決定是否允許對該應用程式授權。
二、權限基本知識2.1 權限的類別
由於基於Linux內核,Android系統中的權限分為以下3類。
(1)Android手機所有者權限
這個和廠商相關,可以理解為系統權限。
(2)Android ROOT權限
類似於Linux,這是Android系統中的最高權限。如果擁有該權限,就可以對Android系統中的任何文件、數據、資源進行任意操作。所謂「越獄」,就是令用戶獲得最高的ROOT權限。
(3)Android應用程式權限
該權限在AndroidManifest文件中由程序開發者聲明,在程序安裝時由用戶授權,共有下述4類不同的權限保護級別(Protection Level)。
2.2 Protection level我們經常在AndroidManifest中使用權限,如果我們想讓應用程式可以發簡訊,那麼應該這樣寫:
<uses-permission android:name="android.permission.SEND_SMS"/>
其權限的定義是在frameworks/base/core/res/AndroidManifest.xml中,如下:
<permission android:name="android.permission.SEND_SMS"
android:permissionGroup="android.permission-group.COST_MONEY"
android:protectionLevel="dangerous"
android:label="@string/permlab_sendSms"
android:description="@string/permdesc_sendSms" />
這個XML可以認為是系統APK使用的AndroidManifest.xml,該APK使用系統的私鑰進行籤名。
下面分別簡單介紹下各個標籤的含義:
android:name:權限的名字,uses-permisson使用的。
android:permissionGroup:權限的分類,在提示用戶安裝時會把某些功能差不多的權限放到一類。
android:protectionLeve:分為Normal、Dangerous、Signature、SignatureOrSystem。
android:label:提示給用戶的權限名。
android:description:提示給用戶的權限描述。
其中android:protectionLevel各個屬性說明如下:
(1)Normal
風險較低的權限,任何應用都可以申請,在安裝應用時,不會直接提示給用戶,點擊全部才會展示。
(2)Dangerous
風險較高的權限,任何應用都可以申請,安裝時需要用戶確認才能使用。
(3)Signature
僅當申請該權限的應用程式與聲明該權限的程序使用相同的籤名時,才賦予該權限。
(4)SignatureOrSystem
僅當申請該權限的應用程式位於相同的Android系統鏡像中,或申請該權限的應用程式與聲明該權限的程序使用相同的籤名時,才賦予該權限。
可以這樣理解:
1)和該APK(定義了這個權限的APK)用相同的私鑰籤名的應用。
2)在/system/app目錄下的應用。
2.3 進程的權限表現
Android是一個多進程系統,在這個系統中,應用程式會在自己的進程中運行,系統和應用之間的安全性是通過Linux進程級別來強制實現的,會給應用程式分配userID和GroupID。
比如我們查看qqdownload這個進程,adb shell後查看下其進程id(紅色部分):根據id,執行下查看狀態,如下:
我們關注如下三行:
Uid:10301 10301 10301 10301
Gid:10301 10301 10301 10301
Groups:1006 1013 1015 1028 3001 3002 3003 9997 50301
這裡我們便看到了系統進程的權限配置信息,這裡的數字具體代表意義,可以在Android
\system\core\include\private\android_filesystem_config.h裡面看到,其部分內容如下:
#define AID_ROOT 0 /* traditional unix root user */
#define AID_SYSTEM 1000 /* system server */
#define AID_RADIO 1001 /* telephony subsystem, RIL */
#define AID_BLUETOOTH 1002 /* bluetooth subsystem */
#define AID_GRAPHICS 1003 /* graphics devices */
#define AID_INPUT 1004 /* input devices */
#define AID_AUDIO 1005 /* audio devices */
#define AID_CAMERA 1006 /* camera devices */
#define AID_LOG 1007 /* log devices */
#define AID_COMPASS 1008 /* compass device */
#define AID_MOUNT 1009 /* mountd socket */
#define AID_WIFI 1010 /* wifi subsystem */
#define AID_ADB 1011 /* android debug bridge (adbd) */
#define AID_INSTALL 1012 /* group for installing packages */
#define AID_MEDIA 1013 /* mediaserver process */
...
其中qqdownload對應的Groups描述如下:
1006 camera devices
1013 mediaserver process
1015 external storage write access
1028 external storage read access
3001 bluetooth: create any socket
3002 bluetooth: create sco,rfcomm or l2cap sockets
3003 can create AF_INET and AF_INET6 sockets
9997 shared between all apps in the same profile
50301 start of gids for apps in each user to share
不同的手機這個信息顯示的不一樣,這個是vivo手機的信息,在華為手機上只有3001 3002 3003 9997 50689。
我們拿其中一個寫SD卡的權限來簡單說明一下:
寫SD卡權限是
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
我們看一個重要的文件,frameworks\base\data\etc\Platform.xml裡的內容:
<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >
<group gid="sdcard_rw" />
</permission>
在看下圖:
對於申請了WRITE_EXTERNAL_STORAGE特權的應用,該應用的進程的gids就包含了sdcard_rw,就可以對sd卡中的文件進行操作了。
再看Android_filesystem_config.h的源碼:
#define AID_SDCARD_RW 1015 /* external storage write access */
我們看到qqdownload進程的Groups裡面有gid為1015,1015就是對應sdcard_rw。
以上介紹了進程的權限表現,實際工作中,我們可能不需要關注這些。
2.4 Android 系統對應用程式權限申請的處理方式分析Android系統對應用程式授權申請的處理流程:
(1)進入處理應用程式授權申請的入口函數;
(2)系統從被安裝應用程式的AndroidManifest.xml文件中獲取該應用正常運行需申請的權限列表;
(3)顯示對話框,請求用戶確認是否滿足這些權限需求;
若同意,則應用程式正常安裝,並被賦予相應的權限;若否定,則應用程式不被安裝。系統僅提供給用戶選擇「是」或者「否」的權利,沒有選擇其中某些權限進行授權的權利。
具體如下圖:
2.5 Android原生權限管理:AppOps2.5.1 AppOps 簡介
AppOps全稱是Application Operations,類似我們平時常說的應用程式的操作權限管理,AppOps是Google原生Android包含的功能,但是Google在每次版本更新時都會隱藏掉AppOps的入口,Google高管Hiroshi Lockheimer的原話:「App ops發布的時機不太對頭,我們需要全面解決問題,而不是單獨地發布Appops」。
在今年的GoogleIO大會上,Google透露AndroidM(Android 6.0)會加入Application Permission Manage的功能,該功能應該就是基於AppOps實現的。
注意:AppOps雖然涵蓋了App的權限管理,但是Google原生的設計並不僅僅是對「權限」的管理,而是對App的「動作」的管理。我們平時講的權限管理多是針對具體的權限(App開發者在Manifest裡申請的權限),而AppOps所管理的是所有可能涉及用戶隱私和安全的操作,包括access notification,keep weak lock,activate vpn,display toast等等,有些操作是不需要Manifest裡申請權限的。
2.5.2 功能效果Setting UI:
AppOps的權限設置是在系統的Settings App裡,Settings->Security->AppOps。
點擊某一app,可以查看該app的權限管理詳情,也可以設置顯示。
使用效果:
AppOps默認給用戶提供了兩個設置選項:
允許該項權限/禁止該項權限
而其實代碼邏輯裡,有三種可選項:
允許/禁止/提示
用戶選擇「提示」選項,則該app在執行這一操作時,系統會給用戶相應的提示,待用戶選擇後app繼續執行。
2.5.3 AppOps總體概覽
核心服務:AppOpsService
系統服務,系統啟動時該服務會啟動運行。
參考以下ActivityManagerService.java,ActivityManagerService啟動過程中:
配置文件:appops.xml、appops_policy.xml
Appops.xml位於/data/system/目錄下,存儲各個app的權限設置和操作信息。
Appops_policy.xml位於/system/etc/目錄下,該文件只在appops strict mode enable時才會存在和使用。
API接口:AppOpsManager
AppOpsService實現了大部分的核心功能邏輯,但它不能被其他模塊直接調用訪問,而是通過AppOpsManager提供訪問接口。
UI層:AppOpsSummary,AppOpsCategory等
上傳UI顯示以及基本邏輯處理。
2.5.4 結構圖
AppOps整體的工作框架基本如下:
Setting UI通過AppOpsManager與AppOpsService交互,給用戶提供入口管理各個app的操作。
AppOpsService具體處理用戶的各項設置,用戶的設置項存儲在/data/system/appops.xml文件中。
AppOpsService也會被注入到各個相關的系統服務中,進行權限操作的檢驗。
各個權限操作對應的系統服務(比如定位相關的Location Service,Audio相關的Audio Service等)中注入AppOpsService的判斷。如果用戶做了相應的設置,那麼這些系統服務就要做出相應的處理。
(比如,LocationManagerSerivce的定位相關接口在實現時,會有判斷調用該接口的app是否被用戶設置成禁止該操作,如果有該設置,就不會繼續進行定位。)
2.5.5 相關API接口
儘管在Android SDK裡能夠看到部分AppOps的API接口,但是Google對此解釋的很清楚:
This API is not generally intended for third party application developers; most features are only available to system applications。Obtain an instance of it throughContext.getSystemService withContext.APP_OPS_SERVICE。
即是說,這些API不是讓第三方app使用的,而是供系統應用調用的。
使用Android SDK開發應用,如果要調用這些API的話,也會編譯不通過。
但是想使用的話,可以嘗試把Android源碼裡AppOpsManager.java打包一下,把jar包導入自己的工程,就可以使用了。
部分重要的API接口如下:
int
checkOp(String op,int uid,String packageName)
Op對應一個權限操作,該接口來檢測應用是否具有該項操作權限。
int
noteOp(String op, int uid,String packageName)
和checkOp基本相同,但是在檢驗後會做記錄。
和checkOp類似,但是權限錯誤,不會拋出SecurityException,而是返回AppOpsManager。MODE_ERRORED。
類似noteOp,但不會拋出SecurityException。
void setMode(int code,int uid,String packageName,int mode)
這個是我們最需要的方法,改變app的權限設置,但偏偏被google隱藏了。
code代表具體的操作權限,mode代表要更改成的類型(允許/禁止/提示)
正常情況下(如果OEM廠商沒有做特殊處理),把AppOpsManager.java打包,引入jar包到工程內,是可以使用上述API接口的,
也即是可以自行設計UI,提供入口來改變app權限。
具體權限對應的code,可以查看AppOpsManager.java源碼裡的描述。
三、權限變化趨勢
Android M之前,應用的權限請求是在安裝時提示,確認後權限就會擁有。
但Android M出來後,將這個權限在運行時做了進一步的檢查,用戶隨時可拒絕權限。
● 從平臺角度看:Android權限集不斷擴展,但不是以提供更細粒度的權限為目標,而是為訪問新的硬體功能提供安全保障。特別地,Dangerous權限集的數量也在不斷增多;
● 從第三方應用和預裝應用角度看:大量應用並未遵守最小特權原則,而是存在大量過多申請權限的情形。值得注意的是:許多預裝應用使用大量高級別的權限,帶來很大的安全隱患。
用戶只有通過不斷學習,充分理解新加入的權限說明,才能在安裝軟體時從Android權限警告中獲取足夠的信息,從而做出正確的決定。鑑於Android系統每隔數月就有較大的版權更新,並引入較多新的權限,這為用戶提出了很高要求。
四、Android M變化以及帶來的影響從Android6.0(API LEVEL23)開始,用戶對應用權限進行授權是發生在應用運行時,而不是在安裝時。這樣可以讓用戶在安裝時節省時間,而且可以更方便的控制應用的權限(至少權限管理不需要ROOT了)。用戶可以按照對應用的需求來控制應用的權限,比如百度地圖的聯繫人權限。同時用戶也可以在應用程式設置中撤銷對應用的權限授權。Android系統中的權限被劃分為兩類:普通權限和敏感權限(更多普通權限、敏感權限及權限組信息:
普通權限不會涉及到用戶隱私,如果應用在manifest文件中直接聲明了普通權限,系統會自動授予權限給應用。比如:網絡INTERNET、藍牙BLUETOOTH、震動VIBRATE等權限。
敏感權限則要獲取到一些用戶私密的信息。如果你的應用需要獲取敏感權限,首先需要獲取用戶的授權。比如:相機CAMERA、聯繫人CONTACTS、存儲設備STORAGE。
https://developer.android.com/reference/android/Manifest.permission.html
詳情見上面這個連結,每個權限裡面可能會有Protection level,標記著是dangerous還是normal
在Android的各個版本中,不論是普通權限還是敏感權限,都需要在manifest文件中聲明,例如權限聲明。然而,在不同版本的作業系統或不同的target SDK level中的結果是不同的。
如果設備運行Android5.1或者更低版本的作業系統,或者你的目標SDK版本號小於或等於22,當你在manifest文件中請求了一些權限,用戶必須在安裝過程時授予全部權限,否則應用不能正常安裝。
如果設備運行在Android6.0或者更高版本,並且目標SDK版本號大於或等於23,應用程式必須要在manifest文件中聲明需要的權限,當程序運行時,它必須要向用戶請求授權每個所需的敏感權限。用戶可以允許或拒絕每個權限,並且程序可以依賴用戶已經授權的權限繼續運行。(這裡可能比較繞,舉個例子:假設你的APP需要聯繫人和拍照權限,在請求權限時用戶只授予了聯繫人權限,那麼當前程序可以正常運行並獲取聯繫人信息,但是無法進行拍照)
註:本篇文章講解如何在API level 23或更高版本並且設備版本為Android6.0或者更高。如果APP的targetSDKVersion為22 或者更低,系統會在安裝或者更新程序時提示用戶授權所有敏感權限。
這裡介紹下幾個常量:
targetSdkVersion:是在程序運行的時候起作用,用於提高指定版本的設備上程序運行體驗。
minSdkVersion和maxSdkVersion:是在程序安裝的時候起作用,用於指定哪些版本的設備可以安裝此應用。
targetAPIleve:是在編譯的時候起作用,用於指定使用哪個API版本(SDK版本)進行編譯。
4.1 PROTECTION_NORMAL類權限
當用戶安裝或更新應用時,系統將授予應用所請求的屬於PROTECTION_NORMAL的所有權限(安裝時授權的一類基本權限),這類權限包括:
只需要在AndroidManifest.xml中簡單聲明這些權限就好,安裝時就授權。不需要每次使用時都檢查權限,而且用戶不能取消以上授權。
4.2 權限組
權限被分組了,如下表:
同一組的任何一個權限被授權了,其他權限也自動被授權。例如,一旦WRITE_CONTACTS被授權了,APP也有READ_CONTACTS和GET_ACCOUNTSG權限了。
4.3 檢查權限如果你的程序需要敏感權限,那麼你必須在每次調用需要該權限的方法時都需要檢查權限。因為用戶隨時都可能會對你程序的某些權限取消授權,所以即使你的應用昨天使用過相機,你也無法確定今天是否還有這個權限。
你可以通過ContextCompat.checkSelfPermission()方法來驗證你的應用是否擁有某個權限。比如,下面的代碼段是檢查是否有擁有寫日曆權限:
// 假設 thisActivity 是當前的 Activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
如果該應用已經獲取到該權限,該方法返回PackageManager.PERMISSION_GRANTED並且程序可以繼續運行。如果該應用未被授予該權限,這個方法會返回PREMISSION_DENIED,同時應用需要明確提示用戶該應用所需要的權限。
4.4 請求權限如果你的應用需要敏感權限並且這些敏感權限已經在manifestm文件中聲明,一定要詢問用戶獲取權限。Android系統提供了幾種請求權限的方法。調用這些方法後,系統會彈出一些Dialog(無需用戶自定義)。
4.5 解釋需要權限的原因在一些應用場景下,你可能想要讓用戶知道需要獲取某個權限的原因。例如,如果用戶使用相冊應用,用戶可能會理解這個應用會需要相機權限,但是用戶可能不會理解為什麼相冊應用還需要獲取位置或者聯繫人。在你請求獲取權限之前,你應該考慮提示用戶。切記不要使用大量解釋;如果你解釋的內容過多,用戶可能會覺得你的應用比較煩人,可能會卸載你的應用…(這段翻譯可能有點問題…)
如果你需要的權限已經被用戶拒絕過一次權限請求,當用戶再次使用需要獲取權限的功能時,應用程式最好向用戶解釋需要對應權限的原因。因為如果用戶一直嘗試使用需要權限的功能,卻一直沒給為該功能對應的權限,說明用戶還沒有明白為什麼應用程式需要這個權限來實現這個功能。在這種情況下可能需要提示用戶需要權限的原因。
Android 系統提供了shouldShowRequestPermissionRationale()方法來幫助開發者判斷是否需要向用戶解釋需要權限的原因。當某條權限之前已經請求過,並且用戶已經拒絕了該權限時,shouldShowRequestPermissionRationale ()方法返回的是true。
注意:如果用戶拒絕某條權限,並且在提示授權的窗口中勾選了不再提示選項時,shouldShowRequestPermissionRationale ()的返回值為false。當某些設備禁止應用程式獲取某些權限時,shouldShowRequestPermissionRationale ()也會返回false。
4.6 向用戶請求獲取應用程式需要的權限如果你的應用程式沒有獲取到它需要的權限,那麼應用程式需要調用該權限對應的requestPermissions()方法,調用requestPermissions()方法時需要傳入一個請求碼(requestCode),這時系統會彈出一個對話框讓用戶選擇是否授權,用戶選擇後,在回調方法onRequestPermissionsResult()中返回對應的請求碼(requestCode)和授權結果。
下面這段代碼檢查應用程式是否有讀聯繫人權限,在未獲取讀聯繫人授權時請求獲取該權限(完整示例見Android_M_Permission):
// thisActivity 為當前 Activity
// 檢查是否已經授權該權限
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 判斷是否需要解釋獲取權限原因
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest。permission。READ_CONTACTS)) {
// 需要向用戶解釋
// 此處可以彈窗或用其他方式向用戶解釋需要該權限的原因
} else {
// 無需解釋,直接請求權限
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest。permission。READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS 是自定義的常量,在回調方法中可以獲取到
}
}
注意:當應用程式調用requestPermissions()方法時,系統會彈出一個對話框給用戶。應用程式不能設置或更改該對話框,如果應用程式需要提供一些信息或者向用戶解釋,需要在調用requestPermissions()方法之前。
4.7 處理請求權限的結果當應用程式請求獲取權限時,系統會彈出一個對話框給用戶。當用戶點擊某個選項時,系統會調用onRequestPermissionResult()方法來傳遞用戶的選擇結果。應用程式需要重寫onRequestPermissionsResult()方法來判斷用戶是否對相應權限授權。。這個回調方法會傳遞一個與requestPermission()方法相同的requestCode。例如,應用程式請求READ_CONTACTS方法,它將會有如下的回調方法:
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
//如果請求被取消,那麼 result 數組將為空
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 已經獲取對應權限
// TODO 執行相應的操作聯繫人的方法
} else {
// 未獲取到授權,取消需要該權限的方法
}
return;
}
// 檢查其他權限....
}
}
授權的對話框顯示的是系統描述的權限組(permission group),它沒有顯示列出詳細的權限列表。比如,如果你請求READ_CONTACTS權限,系統對話框只會提示用戶應用程式需要獲取聯繫人權限,用戶只需要給每個權限組授權一次。如果應用程式請求獲取一個權限組的其他權限(在manifest文件中聲明的權限),系統會自動授予該權限。當你請求這個權限時,系統會調用onRequestPermissionResult(),回調方法並且傳遞PERMISSION_GRANTED,這跟用戶在彈窗中點擊授予權限的按鈕的流程是相同的。
注意:應用程式還是需要明確的請求它所需要的每個權限,即使用戶已經授予了跟這個權限在同一個permission group的其他權限。除此之外,對某個權限組的授權可能會改變。程序的代碼不能依賴於用戶已經對某個權限組授權的假設。
例如,應用程式在manifest 文件用聲明了READ_CONTACTS和WRITE_CONTACTS權限,如果應用程式請求了READ_CONTACTS權限並且用戶授予了該權限,那麼當應用程式請求WRITE_CONTACTS權限時,系統會自動授予應用程式該權限。
譯者註:READ_CONTACTS和WRITE_CONTACTS都屬於CONTACTS權限組。更多關於權限組信息可以訪問permission group或直接看我的截圖:權限和權限組
如果用戶拒絕了一個應用權限請求,那麼應用程式應該進行適當的操作。例如:應用程式可以彈出一個對話框來解釋為什麼用戶不能執行需要該權限的操作。
當系統提示用戶給應用程式授權權限時,會給用戶提供一個不再提示的選項來通知系統不再針對該權限進行詢問。用戶勾選該選項後,當應用程式請求獲取對應權限時,系統會立即拒絕授權。系統會調用onRequestPermissionResult()回調方法並且傳遞PERMISSION_DENIED參數,就像用戶拒絕授權一樣。這意味著,當你調用requestPermissions()方法時,你不能假定應用程式會跟用戶直接交互。
原理篇就介紹到這裡,後續還有一個實踐篇,有興趣的請參閱~
長按指紋識別圖中的二維碼,獲取更多測試乾貨分享!