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)),

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,566评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 此刻 她躺在我的怀里 睡着了 安详像只憩身在地洞里的兔子 她一定梦到了什么 要把腿用力的揉进我的身体 又或者根本没...
    光皂阅读 249评论 0 0
  • “李羊,李羊,你快看那个五彩斑斓的地方是卖什么的?是不是一家糖果店啊?!” 上一章节 第八章 “我们临走的那天,爸...
    火把山阅读 166评论 2 6
  • 【易经原文·坤卦二爻爻辞及象传】 [爻辞]六二:直、方、大(1)。不习无不利(2)。[象传]象曰:六二之动,直以方...
    大珊老师阅读 1,239评论 1 3