<p> 總結整理了一下android權限相關的知識,由于篇幅過長,分為兩篇博客來寫,上篇博客主要是詳解權限和安全,下篇主要是介紹android6.0權限適配問題:
<a >android permission權限與安全機制解析(下)</a></p>
<h1 id="uses-permission">uses-permission</h1>
<p> 用法為<uses-permission android:name=”string” android:maxSdkVersion=”integer”/>
為了保證application的正常運行,需要系統授予app的權限聲明。這個權限是在用戶安裝應用的時候授予的。
android:name的值可以是其他app通過<permission>聲明的(用于兩個應用之間的交互),也可以是系統的權限名稱,例如
android.permission.CAMERA或android.permission.READ_CONTACTS等等。需要注意的一點是uses-permission的權限要求說明,可能會引起app在Android Market中的過濾。
android:maxSdkVersion用來標注該權限所支持的最大api版本號,如果當從某個特定版本時,不需要該權限時就可以加上該限制。
系統權限列表有很多,各自的用途也不一樣,有幾個鏈接可以參考一下:
<a title="http://blog.csdn.net/zjl5211314/article/details/6595080">http://blog.csdn.net/zjl5211314/article/details/6595080</a>
<a title="http://developer.android.com/reference/android/Manifest.permission.html">http://developer.android.com/reference/android/Manifest.permission.html</a>
<a title="http://developer.android.com/guide/topics/security/permissions.html">http://developer.android.com/guide/topics/security/permissions.html</a>
<a title="http://developer.android.com/guide/topics/manifest/uses-permission-element.html">http://developer.android.com/guide/topics/manifest/uses-permission-element.html</a>
</p>
<h1 id="自定義permission">自定義permission</h1>
<h2 id="permission標簽">permission標簽</h2>
<p>
<permission android:description=”string resource”
android:icon=”drawable resource”
android:label=”string resource”
android:name=”string”
android:permissionGroup=”string”
android:protectionLevel=[“normal” | “dangerous” |
“signature” | “signatureOrSystem”] />
android:description:對權限的描述,比lable更加的詳細,介紹該權限的相關使用情況,比如當用戶被詢問是否給其他應用該權限時。注意描述應該使用的是string資源,而不是直接使用string串。
android:icon:用來標識該權限的一個圖標。
android:label:權限的一個給用戶展示的簡短描述。方便的來說,這個可以直接使用一個string字串來表示,但是如果要發布的話,還是應該使用string資源來表示。
android:name:權限的唯一名字,由于獨立性,一般都是使用包名加權限名,該屬性是必須的,其他的可選,未寫的系統會指定默認值。
android:permissionGroup: 權限所屬權限組的名稱,并且需要在這個或其他應用中使用<permission-group>標簽提前聲明該名稱,如果沒有聲明,該權限就不屬于該組。
android:protectionLevel:權限的等級</p>
<ul>
<li>normal</li>
低風險權限,只要申請了就可以使用(在AndroidManifest.xml中添加<uses-permission>標簽),安裝時不需要用戶確認;
<li>dangerous</li>
高風險權限,安裝時需要用戶的確認才可使用;
<li>signature</li>
只有當申請權限的應用程序的數字簽名與聲明此權限的應用程序的數字簽名相同時(如果是申請系統權限,則需要與系統簽名相同),才能將權限授給它;
<li>signatureOrSystem</li>
簽名相同,或者申請權限的應用為系統應用(在system image中),與signature類似,只是增加了rom中自帶的app的聲明 ,盡量不要使用該選項,因為signature已經適合絕大部分的情況。
</ul>
<p> 對于普通和危險級別的權限,我們稱之為低級權限,應用申請即授予。其他兩級權限,我們稱之為高級權限或系統權限。當應用試圖在沒有權限的情況下做受限操作,應用將被系統殺掉以警示。系統應用可以使用任何權限。權限的聲明者可無條件使用該權限。
下面通過指定一個BroadcastReceiver的權限來實驗,首先創建了兩個app:app A ,app B 。app A中注冊了一個BroadcastReceiver ,app B 發送消息,app A的manifest文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testbutton" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15" /> <!-- 聲明權限 --> <permission android:name="com.example.testbutton.RECEIVE" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" launcheMode="singleTask" android:configChanges="locale|orientation|keyboardHidden" android:screenOrientation="portrait" android:theme="@style/android:style/Theme.NoTitleBar.Fullscreen" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 注冊Broadcast Receiver,并指定了給當前Receiver發送消息方需要的權限 --> <receiver android:name="com.example.testbutton.TestButtonReceiver" android:permission="com.example.testbutton.RECEIVE" > <intent-filter> <action android:name="com.test.action" /> </intent-filter> </receiver> </application></manifest>
<p> app B 的manifest 文件內容
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testsender" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15" /> <!-- 聲明使用指定的權限 --> <uses-permission android:name="com.example.testbutton.RECEIVE" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/title_activity_main" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
這樣app B 給app A 發送消息,A就可以收到了,若未在app B的manifest文件中聲明使用相應的權限,app B發送的消息,A是收不到的。 另外,也可在app B 的manifest文件中聲明權限時,添加android:protectionLevel=“signature”,指定app B只能接收到使用同一證書簽名的app 發送的消息。 </p>
<h2 id="permission-tree標簽">permission-tree標簽</h2>
<p> <permission-tree android:icon=”drawable resource”
android:label=”string resource”
android:name=”string” />
該標簽包含于在< manifest >標簽中。
該標簽聲明權限樹的基礎名稱。 應用程序擁有樹中的所有名稱。 可以通過調用 PackageManager.addPermission() 在權限樹中動態添加新的權限。 樹中的名稱以句點(’.’)分隔。 比如,假定基礎名稱為com.example.project.taxes,則可加入類似以下格式的權限:
com.example.project.taxes.CALCULATE
com.example.project.taxes.deductions.MAKE_SOME_UP
com.example.project.taxes.deductions.EXAGGERATE
注意本元素并不是聲明權限,而只是為后續要加入的權限定義一個命名空間。</p>
<p> android:icon
代表樹中所有權限的圖標。 本屬性必須設為對 Drawable 資源的引用。
android:label
供用戶閱讀的權限組名稱。 為了方便起見可以將其直接設為字符串, 但在應用程序準備發布時,應該設為對字符串的引用,以便對其進行本地化。
android:name
權限樹的基礎名稱,用作樹中所有權限的前綴。 為了保證名稱的唯一性,應該采用 Java 風格的域名規則。 名稱的路徑必須至少包含兩個句點分割的字段 — 比如:com.example.base 可以,但 com.example 就不行。</p>
<h2 id="permission-group標簽">permission-group標簽</h2>
<p> Android在定義 permission 時, 為每個Permission都進行了分組,一個權限主要包含三個方面的信息:權限的名稱;屬于的權限組;保護級別。一個權限組是指把權限按照功能分成的不同的集合。每一個權限組包含若干具體 權限,例如在 COST_MONEY 組中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和費用相關的權限。具體如下SDK所示:
<a title="http://developer.android.com/reference/android/Manifest.permission_group.html">http://developer.android.com/reference/android/Manifest.permission_group.html</a>
再來看看源碼(在frameworks/base/core/res /AndroidManifest.xml):
<!-- Used for permissions that can be used to make the user spend money without their direct involvement. For example, this is the group for permissions that allow you to directly place phone calls, directly send SMS messages, etc. --> <permission-group android:name="android.permission-group.COST_MONEY" android:label="@string/permgrouplab_costMoney" android:description="@string/permgroupdesc_costMoney" /> <!-- Allows an application to send SMS messages. --> <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" /> <!-- Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call being placed. --> <permission android:name="android.permission.CALL_PHONE" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" android:label="@string/permlab_callPhone" android:description="@string/permdesc_callPhone" />
可以看到,這邊先定義了一個permissiongroup : android.permission-group.COST_MONEY, 然后又定義了兩個permission :android.permission.SEND_SMS 和 android.permission.CALL_PHONE , 需要注意的是,這兩個權限中都有一個android:permissionGroup屬性,這個屬性就指定了這個權限所屬的PermissionGroup。</p>
<p> android:description
這個屬性用于給權限組定義一個用戶可讀的說明性文本。這個文本應該比標簽更長、更詳細。這個屬性不應該直接使用字串,而應該使用字符串引用。
android:icon
這個屬性定義了一個代表權限的圖標,這個屬性應該使用資源文件來定義。
android:label
這個屬性給權限組定義了一個用戶可讀的名稱。
android:name
這個屬性定義了權限組的名稱,它是能夠分配給<permission>元素的permissionGroup屬性的名稱。
</p>
<h1 id="安全機制">安全機制</h1>
<h2 id="組件權限">組件權限</h2>
<p> Android是一個權限分離的系統,這是利用Linux已有的權限管理機制,通過為每一個Application分配不同的uid和gid,從而使得不同的Application之間的私有數據和訪問(native以及java層通過這種sandbox機制,都可以)達到隔離的目的 。與此同時,Android 還在此基礎上進行擴展,提供了permission機制,它主要是用來對Application 可以執行的某些具體操作進行權限細分和訪問控制,同時提供了per-URI permission 機制,用來提供對某些特定的數據塊進行ad-hoc方式的訪問。
通過AndroidManifest.xml文件可以設置高級權限,以限制訪問系統的所有組件或者使用應用程序。所有的這些請求都包含在你所需要的組件中的 android:permission屬性,命名這個權限可以控制訪問此組件。</p>
<ol>
<li>Activity 權限 (使用<activity>標簽) 限制能夠啟動與Activity權限相關聯的組件或應用程序。在Context.startActivity()和Activity.startActivityForResult()期間檢查;</li>
<li>Service 權限(應用<service>標簽)限制啟動、綁定關聯服務的組件或應用程序。此權限在Context.startService(),Context.stopService() 和 Context.bindService() 期間要經過檢查;</li>
<li>BroadcastReceiver 權限(應用<receiver>標簽)能夠為相關聯的接收者組件或應用程序設置限制。在 Context.sendBroadcast() 返回后此權限將被檢查,同時系統設法將廣播遞送至相關接收者。因此,權限失敗將會導致拋回給調用者一個異常;它將不能遞送到目的地。為了接收你的廣播,你請求一個接收器的應用程序必須持有要求的那個權限(如上面例子所示),而且sendBroadcast的相關幾個函數中也可以加入permission參數用來指定只帶有該permission的接受者可以接受該廣播。</li>
<li>ContentProvider 權限(使用 <provider> 標簽)用于限制能夠訪問 ContentProvider 中的數據的組件或應用程序。如果調用者沒有請求權限,那么會為調用拋出一個安全異常( SecurityException )。數據庫本身的讀寫可以處理多線程問題,但是數據的先后可以考慮同步問題,設置android:multiprocess=”true”屬性來保證數據的正確性</li>
</ol>
<p> 對于組件而言,除了使用權限限制與外部交互的實體外,還有一個屬性也具有該功能,那就是android:exported,這個屬性用于指示該服務是否能夠被其他應用程序組件調用或跟它交互。如果設置為true,則能夠被調用或交互,否則不能。設置為false時,只有同一個應用程序的組件或帶有相同用戶ID的應用程序才能啟動或綁定該服務。
它的默認值依賴于該服務所包含的過濾器。沒有過濾器則意味著該服務只能通過指定明確的類名來調用,這樣就是說該服務只能在應用程序的內部使用(因為其他外部使用者不會知道該服務的類名),因此這種情況下,這個屬性的默認值是false。另一方面,如果至少包含了一個過濾器,則意味著該服務可以給外部的其他應用提供服務,因此默認值是true。</p>
<h2 id="權限檢測">權限檢測</h2>
<p> 首先是root用戶和system用戶擁有所有的接口調用權限,然后對于其它用戶可以使用以下這幾個函數來檢測 :
<a >PackageManager.checkPermission (String permName, String pkgName)</a>
用來檢測一個package中是否授予了指定permission。
<a >Context.checkCallingOrSelfPermission (String permission)</a>
用來檢測自己或者調用進程中是否授予了指定permission。
<a >Context.checkCallingOrSelfUriPermission (Uri uri, int modeFlags)</a>
用來檢測自己或者調用進程中是否授予了一個uri通過modeFlags指定的permission。
<a >Context.checkCallingPermission (String permission)</a>
檢查正在處理的調用者進程是否授予指定permission 權限,如果調用者是自己那么返回 。
<a >Context.checkCallingUriPermission (Uri uri, int modeFlags)</a>
用來檢測調用進程中是否授予了一個uri通過modeFlags指定的permission。
<a >Context.checkPermission (String permission, int pid, int uid)</a>
用來檢測指定uid和pid的進程中是否授予了指定的permission。
<a >checkSelfPermission (String permission)</a>
23版本api添加,用來檢測自己是否授予了指定permission
<a >Context.checkUriPermission (Uri uri, int pid, int uid, int modeFlags)</a>
用來檢測指定uid和pid的進程中是否授予了一個uri通過modeFlags指定的permission。
<a >Context.checkUriPermission (Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags)</a>
比上面的方法多了一個檢測permission的功能,相當于同時調用checkPermission(String, int, int)和checkUriPermission(Uri, int, int, int)。
-------------
下面這一組和上面一一對應,區別就在于如果遇到檢查不通過時,會拋出異常,打印消息 :
<a >Context.enforceCallingOrSelfPermission(String permission, String message)</a>
<a >Context.enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)</a>
<a >Context.enforceCallingPermission (String permission, String message)</a>
<a >Context.enforceCallingUriPermission (Uri uri, int modeFlags, String message)</a>
<a >Context.enforcePermission (String permission, int pid, int uid, String message)</a>
<a >Context.enforceUriPermission (Uri uri, int pid, int uid, int modeFlags, String message)</a>
<a >Context.enforceUriPermission (Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags, String message)</a>
-----------
<a >Context.grantUriPermission (String toPackage, Uri uri, int modeFlags)</a>
為指定package添加訪問指定uri 的讀或者寫權限
<a >Context.revokeUriPermission (Uri uri, int modeFlags)</a>
為指定package刪除訪問指定uri 的讀或者寫權限。
以上函數中check開頭的,只做檢查。enforce開頭的,不單檢查,沒有權限的還會拋出異常。
checkPermission相關函數</p><ol><li>如果傳入的 permission 名稱為 null ,那么返回PackageManager.PERMISSION_DENIED 。</li>
<li>判斷調用者uid是否符合要求 。</li></ol><p></p>
<ul><li>如果uid為0,說明是root權限的進程,對權限不做控制。</li>
<li>如果uid為system server進程的uid,說明是system server,對權限不作控制。</li>
<li>如果是 ActivityManager 進程本身,對權限不作控制。</li>
<li>如果調用者uid與參數傳入的req uid不一致,那么返回PackageManager.PERMISSION_DENIED。</li></ul>
<li>如果通過 2 的檢查后,再調用 PackageManagerService.checkUidPermission ,判斷這個uid是否擁有相應的權限,分析如下 :<ul> <li>首先它通過調用getUserIdLP,去PackageManagerService.Setting.mUserIds數組中,根據uid查找uid(也就是package)的權限列表。一旦找到,就表示有相應的權限。</li>
<li> 如果沒有找到,那么再去PackageManagerService.mSystemPermissions中找。這些信息是啟動時,從/system/etc/permissions/platform.xml中讀取的。這里記錄了一些系統級的應用的 uid 對應的 permission 。</li>
<li>返回結果 。</li></ul></li> CheckUriPermission函數<ol><li>如果uid為0,說明是root用戶,那么不控制權限。</li>
<li>否則,在ActivityManagerService維護的mGrantedUriPermissions這個表中查找這個uid是否含有這個權限,如果有再檢查其請求的是讀還是寫權限。</li></ol>
<h2 id="uri權限"> URI權限</h2>
到目前為止我們討論的標準的permission系統對于content provider來說是不夠的。一個content provider可能想保護它的讀寫權限,而同時與它對應的直屬客戶端也需要將特定的URI傳遞給其它應用程序,以便其它應用程序對該URI進行操作。一個典型的例子就是郵件程序處理帶有附件的郵件。進入郵件需要使用permission來保護,因為這些是敏感的用戶數據。然而,如果有一個指向圖片附件的URI需要傳遞給圖片瀏覽器,那個圖片瀏覽器是不會有訪問附件的權利的,因為他不可能擁有所有的郵件的訪問權限。
針對這個問題的解決方案就是per-URI permission:當啟動一個activity或者給一個activity返回結果的時候,呼叫方可以設置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION (另外一篇intent博客中也有介紹到)。這會使接收該intent的activity獲取到進入該Intent指定的URI的權限,而不論它是否有權限進入該intent對應的content provider。
這種機制允許一個通常的capability-style模型,這種模型是以用戶交互(如打開一個附件, 從列表中選擇一個聯系人)為驅動,特別獲取fine-grained permissions(更細?;臋嘞蓿_@是一種減少不必要權限的重要方式,這種方式主要針對的就是那些和程序的行為直接相關的權限。
這些URI permission的獲取需要content provider(包含那些URI)的配合。強烈推薦在content provider中提供這種能力,并通過android:grantUriPermissions或者<grant-uri-permissions>標簽來聲明支持。
<h2 id="androidshareduserid">android:sharedUserId</h2>
安裝在設備中的每一個apk文件,Android給每個APK進程分配一個單獨的用戶空間,其manifest中的userid(userid的特點: 作為APK身份的標識 ;userid對應一個Linux用戶,所以不同APK(用戶)間互相訪問數據默認是禁止的)就是對應一個Linux用戶都會被分配到一個屬于自己的統一的Linux用戶ID,并且為它創建一個沙箱,以防止影響其他應用程序(或者其他應用程序影響它)。用戶ID 在應用程序安裝到設備中時被分配,并且在這個設備中保持它的永久性。所以讓兩個apk使用相同的userID,這樣它們就可以看到對方的文件,如果在此基礎之上再加上相同的android:process,他們就可以運行在一個進程中,能夠做更多的事情了。為了節省資源,具有相同ID的apk也可以在相同的linux進程中進行(注意,并不是一定要在一個進程里面運行),共享一個虛擬機。
所以當兩個應用公用一個userid的時候就可以使用SharedPreference之類的機制進行文件數據共享:
//第一個應用程序為的menifest文件代碼如下:<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.android.demo_A"android:versionCode="1"android:versionName="1.0"android:sharedUserId="com.android.test">//第二個應用程序的menifest文件代碼如下:<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.android.demo_B"android:versionCode="1"android:versionName="1.0"android:sharedUserId="com.android.test">
上面給出了兩個程序的menifest定義,兩者共用一個ShareUserId,下面我們看看從一個程序里面如何獲取另外一個程序的Context。假設我們從package=“com.android.demo_A”的程序獲取package=”com.android.demo_B”的程序的context:
Context ct=this.createPackageContext("com.android.demo_B", Context.CONTEXT_IGNORE_SECURITY);
這樣我們就能夠獲取到另外一個應用的Application context了,獲取到上下文之后就能夠實現數據共享和通信,具體可以查看我的 IPC通信博客。
<h1 id="總結">總結</h1>
只要signature相同,就算不顯式聲明<ues-permission>也能access設定了normal或dangerous權限設定的數據或功能。
擁有system級別權限的使用者可以access其他普通signature權限聲明設定過的功能。所以,設定為擁有system級別權限即可。
應用程序安裝的時候,應用程序請求的permissions是通過package installer來批準獲取的。package installer是通過檢查該應用程序的簽名來確定是否給予該程序request的權限。在用戶使用過程中不會去檢查權限,也就是說要么在安裝的時候就批準該權限,使其按照設計可以使用該權限;要么就不批準,這樣用戶也就根本無法使用該feature,也不會有任何提示告知用戶嘗試失敗。
例如高級權限用有system級別權限設定的api時,需要使其apk擁有system權限。比如在 android 的API中有供給SystemClock.setCurrentTimeMillis()函數來修改系統時間。有兩個方法:
第1個方法簡單點,不過需要在Android系統源碼的情況下用make來編譯:
<ul>
<li>在應用程序的AndroidManifest.xml中的manifest節點中插手android:sharedUserId=”android.uid.system”這個屬性。</li>
<li>修改Android.mk文件,插手LOCAL_CERTIFICATE := platform這一行</li>
<li>使用mm命令來編譯,生成的apk就有修改系統時間的職權范圍了</li>
</ul>
<p>第2個方法麻煩點,不過不用開虛擬機跑到源碼環境下用make來編譯:</p>
<ul>
<li>同上,插手android:sharedUserId=”android.uid.system”這個屬性。</li>
<li>使用eclipse編譯出apk文件,但是這個apk文件是不能用的。</li>
<li>用壓縮軟件打開apk文件,刪掉META-INF目錄下的CERT.SF和CERT.RSA兩個文件。</li>
<li>使用目標系統的platform密鑰來重新給apk文件簽名。這步比較麻煩,首先找到密鑰文件,在我的Android源碼目錄中的位置是”build/target/product/security”,下面的platform.pk8和platform.x509.pem兩個文件。然后用Android提供的Signapk工具來簽名,signapk的源代碼是在”build/tools/signapk”下,用法為”signapk platform.x509.pem platform.pk8 input.apk output.apk”,文件名最好使用絕對路徑防止找不到,也可以修改源代碼直接使用。</li>
</ul>
<h1 id="引用文章">引用文章:</h1>
<blockquote>
<p><a >http://blog.csdn.net/xyz_lmn/article/details/7372040</a>
<a >http://blog.csdn.net/t12x3456/article/details/7749200</a>
<a >http://yelinsen.iteye.com/blog/1012740</a>
<a >http://berdy.iteye.com/blog/1782854</a>
<a >http://my.oschina.net/u/589963/blog/316912</a>
<a >http://blog.csdn.net/vshuang/article/details/44001661</a>
<a >http://www.chawenti.com/articles/12078.html</a>
<a >http://ee.ofweek.com/2012-04/ART-8300-2808-28609680_4.html</a>
<a >http://blog.csdn.net/liujian885/article/details/5404834</a>
<a >http://maoruibin.github.io/%E6%8A%80%E6%9C%AF/2015/11/10/android_m_permission.html</a>
<a >https://www.zhihu.com/question/37317693</a>
<a >http://blog.csdn.net/wirelessqa/article/details/8581652</a></p>
</blockquote>