0104 - Android 简介 - 系统权限

来源:互联网 发布:vim如何c语言高亮 编辑:程序博客网 时间:2024/06/07 05:03

系统权限

来源:Android Develop - API Guides - Introduction - System Permissions

Android 是一个权限分离的操作系统,其中的每个应用运行时都会带有一个单独的系统标识(Linux 的 user ID 和 group ID)。系统的每个部分也被分离成了不同的身份,这样Linux系统就可以将每个应用从系统中分离出来。

而 "permission" 机制则提供了更加细节的安全功能, 它强行限制了进程所能够执行的操作,而授予特定的权限则可以使应用去访问指定的数据。

这篇文档描述了应用开发者如何使用Android提供的安全功能,如果还想了解更多可以查看 Android Security Overview。

安全架构


Android 的安全架构设计核心是,在默认情况下,没有应用可以执行对其他应用、操作系统以及用户有不利影响的操作,包括读写用户私人数据(s例如联系人和邮箱)、读写其他应用的文件、执行网络连接、保持设备处于唤醒状态等等。

应为每个 Android 应用都运行在独立的进程中,因此应用必须要明确地共享数据和资源,应用通过声明需要的权限来获取进程内无法提供的功能,应用需要静态地申请所需要的权限,而 Android 系统则询问用户是否同意授予权限。

应用运行环境和构建应用的技术 (Java、Native、混合型) 不相关,特别是 Dalvik VM (应用的运行环境)不能提供一个安全的边界,任何应用都可以运行 Native 代码(详见 the Android NDK),因此所有类型的应用 (Java、Native、混合型) 以同样的方式运行,在安全性方面是相同的。

应用签名


所有的APK必须要用证书来签名,APK的开发者持有证书的密钥,证书代表了应用的作者。证书不需要专门的机构来签名,对于Android应用而言,使用个人签名的证书就已经是典型的、完美的许可了。 Android中使用证书的目的是为了区分应用的作者,这可以让系统在应用申请一些签名的权限或者在应用请求和其他应用的具有相同的Linux标识ID时进行授权或者拒绝。

用户ID和文件访问


在安装时,Android会给每个 package 分配一个单独的 Linux 用户ID,这个ID 在 package 的生命周期内保持不变。在不同的设备上,相同的 package 可能会有不同的用户 ID,这和每个包在一台设备上都需要分配一个单独的用户ID有关。

因为Android的安全机制是进程级的,所以如果两个 package 的用户 ID 不同的话,通常情况下它们是不可以运行在一个进程中的。我们可以使用每个 package 里面的  AndroidManifest文件中的manifest 标签中的 sharedUserId 属性来为不同的package 指定相同的用户ID,这样做之后,这些 package 会被系统当成一个应用而拥有相同的安全性、用户ID以及文件的权限。注意,为了保证安全,只有拥有相同签名 (且拥有相同的sharedUserId)的两个应用才会被系统分配相同的用户ID。

应用存储的任何数据都会被指定当前应用的用户ID,通常是不可以被其他应用获取到的。当调用 getSharedPreferences(String, int)、 openFileOutput(String, int) 或者 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)方法创建文件时,我们可以使用MODE_WORLD_READABLE 且/或 MODE_WORLD_WRITEABLE 来允许任何其他应用读写文件,设置这些标签后,文件仍然属于当前应用,但它的全局读写权限已经被设置了,因此对其他应用而言也是可见的。

用户权限


默认的Android应用是没有与之相关联的权限的,这意味着它不能做任何影响用户体验或影响了设备数据的事情,为了能够使用设备中被保护的功能,我们必须要在AndroidManifest文件中加入<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>

如果应用在AndroidManifest文件中声明了普通的权限 (不会对用户隐私以及设备运行造成过多影响),那么系统会自动授予这些权限。如果应用在AndroidManifest文件中声明了危险的权限 (可能会对用户隐私以及设备运行造成严重影响),那么系统会询问用户的同意是否确定授予这些权限,而系统如何向用户请求权限则依赖于设备当前的系统版本以及应用的目标版本:

  • 如果设备运行在 Android 6.0 (API level 23) 或更高版本时,且应用的 targetSdkVersion (目标版本)为 23 或者更高,那么这些危险的权限需要应用去动态申请,用于也可以在任何时间移除这些权限,所以应用需要在运行时去检查这些权限。如需了解更多关于权限的动态申请,请查阅 API Traning - Working with System Permissions 。
  • 如果设备运行在 Android 5.1 (API level 22) 或更低版本时,或者应用的 targetSdkVersion  (目标版本)为22或者更低,那么系统会在应用安装时询问用户是否授予权限,如果之后你在这个应用的更新版本中添加了新的权限,那么系统会在用户更新应用时询问用户是否授予权限。一旦用户安装了应用之后,那么用户想要取消这些权限的唯一方式是卸载应用。

通常需要权限而应用未获取权限的话会抛出 SecurityException 异常,然而,并不保证在任何地方都会发生这种事情。例如, sendBroadcast(Intent) 会在方法调用返回之后才去检查每个接收的receiver的权限,此时数据已经送到了receiver那里,所以即便有权限异常的问题也不会抛出异常。当然在大多数情况下,权限异常会打印到系统日志中。 

Android提供的所有权限可以在 Manifest.permission (SDK API文档中有)这里找到,任何应用也可以自定义并使用其定义的权限,所以那个权限列表里并不包含所有可能出现的权限。

程序运行期间的任何地方都可能会用到权限:

  • 当电话打进来时,阻止应用的核心功能。
  • 当开启一个Activity时,打开其他应用的Activity
  • 收发广播时,选择谁能够接收广播以及谁可以发送广播
  • 获取或操作一个ContentProvider。
  • 绑定或启动Service。

自动调整权限

随着时间的推移,新的限制会逐渐加入到系统中,例如,为了使用核心API,应用必须获取之前并不需要去做申请的权限,由于已经存在的应用是假设这些权限并不需要去申请的基础上构建的,所以Android会自动给这些应用添加权限以避免应用无法再设备中运行。系统是否会帮助应用自动添加权限取决于应用的 targetSdkVersion (目标版本)属性,如果这个值比加入权限的系统版本低的话,那么系统就会将这些权限自动添加给应用。

例如, WRITE_EXTERNAL_STORAGE 权限在 API level 4 中添加到系统中的,用于限制存储空间的共享,如果应用的targetSdkVersion 为3或者更低,那么系统会自动添加这个权限给应用。

提醒: 那么当应用发布在Google Play时,会列出所有自动添加给应用的权限,即便应用并没有使用这些权限。(意味着所有高于应用版本的权限都会强行自动添加给应用)

为了避免这种情况并且移除那些我们不需要的权限,请经常更新应用的 targetSdkVersion 到最新版本,可以在 Build.VERSION_CODES 文档中查询每个系统版本中加入了哪些权限。

正常的权限和危险的权限


系统权限在安全性上分为几个等级,最重要的两个等级为normal (普通)权限 和 dangerous (危险)权限:

  • 普通权限包含了可以获取进程外部的数据和资源,且这些数据对用户隐私和设备运行的影响非常小,例如,设置时区就是一个普通的权限。如果应用声明了一个普通的权限,系统会自动将权限授予给用户。如需查阅当前所有普通权限请看Normal permissions。
  • 危险权限包含了可以获取包含用户隐私的数据和资源,或者可能影响用户存储的数据,以及影响设备运行的行为,例如,读取联系人就是一个危险的权限,如果应用声明了一个危险的权限,那就需要用户明确地将权限授予给用户

权限组

所有危险的权限都属于权限组,如果设备运行在 Android 6.0 (API level 23) 且应用的 targetSdkVersion 为23或更高,那么在应用请求危险权限时系统会做出下面的相应:

  • 如果应用在AndroidManifest中声明了一个危险的权限,且应用中这个权限所对应的权限组还没有这个权限时,那么系统会显示一个对话框来描述用户这个权限组,而对话框中不会描述应用申请的那条具体的权限。例如,如果应用请求 READ_CONTACTS 权限,那么动态申请权限时显示的对话框仅仅会显示应用需要获取设备联系人,如果用户批准,那么设备就会授予应用相应的权限。
  • 如果应用在AndroidManifest中声明了一个危险的权限,且应用中这个权限所对应的权限组已经获取了其他权限,那么系统会立即授予应用权限而不会询问用户。例如,如果应用之前申请且被授予了 READ_CONTACTS 权限,那么之后再申请 WRITE_CONTACTS 权限时,系统会直接授予应用这个权限。

任何权限都属于一个权限组,包括普通权限以及自定义权限,然而,只有危险的权限组才可能会影响用户体验,所以对于普通权限而言,我们完全可以忽略权限组这回事。

如果设备运行在 Android 5.1 (API level 22) 或更低版本,或者应用的 targetSdkVersion 为22或者更低,那系统会在应用安装时询问用户是否授予权限。同样的,系统会向用户描述权限组而不是某个具体的权限。

表1. 危险的权限和权限组

权限组权限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
  • RECEIVE_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.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>

注意: 系统不允许多个package声明相同名称的权限,除非这些包具备相同的签名证书T,如果一个package定义了一条权限,系统将不允许其他定义了相同名称的权限且签名证书不同的应用安装到设备上 ,为避免自定义的权限名称冲突,我们建议使用域名逆序的格式来命名自定义权限,例如 com.example.myapp.ENGAGE_HYPERSPACE。

protectionLevel 属性是必须的, 用于告知系统如何向用户说明这个权限的信息或者告诉用户谁可以持有这个权限,具体请看链接的文档protectionLevel。

android:permissionGroup 属性是可选的,仅仅用于帮助系统向用户显示权限,尽管我们可以自定义权限组,但大多数情况下我们会希望将这个权限放到标准的系统权限组中 (android.Manifest.permission_group列出的权限), 使用当前已经存在的权限组会更好一些,因为这样可以简化展示给用户的权限UI。

我们需要提供权限的标签以及描述,这两个都是string资源,其中 权限标签 (android:label) 是可以在权限列表中看到的权限名字而 权限描述( android:description)则是对权限的细节描述。 标签应该尽可能简短——用几个字来描述权限保护的功能的关键词,而描述则应该是用于说明权限可以让获取者做些什么的几句话,惯例是用2句话来描述,第一句用来具体描述权限,第二句提醒用户如果应用授予了权限可能会发生的不好的结果。

这是 CALL_PHONE 权限的标签和描述的例子:

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

我们可以使用 Settings 应用或者shell 命令adb shell pm list permissions来浏览当前系统中定义的所有权限。如果查看每个应用的权限的话,用 Settings 应用,在 Settings > Applications 里面,选择一个应用可以查看它用到的权限,对于开发者而言,可以用 adb -s 命令 来查看每个应用用到的权限:

$ adb shell pm list permissions -sAll Permissions:Network communication: view Wi-Fi state, create Bluetooth connections, fullInternet access, view network stateYour location: access extra location provider commands, fine (GPS) location,mock location sources for testing, coarse (network-based) locationServices that cost you money: send SMS messages, directly call phone numbers...

自定义权限的建议

应用可以自定义自己的权限且其他应用可以在 <uses-permission> 元素中申请这条权限,然而,我们要谨慎的评估我们的应用是否需要其他应用自定义的权限。

  • 如果你正在设计一组应用且准备将功能暴露给另一个应用,尽量让自定义的权限只定义一次,如果这些应用不具有相同的签名那更要这样做,即便这些应用有相同的签名,权限之定义一次也是最好的选择。
  • 如果只希望功能对具有相同签名的应用可用,你可以避免通过签名检查来自定义权限,当你的应用向你的另一个应用请求时,另一个应用在答复时会先核实两个应用是否具有相同的签名。
  • 如果你正在设计一组只在你的设备上运行的应用,你应该开发安装一个管理所有你的应用的权限的 package ,这个 package 不需要提供任何服务,它仅仅声明所有权限,并且这些应用都在 <uses-permission> 元素在请求这些权限。

在 AndroidManifest.xml 文件中获取权限

你可以通过 AndroidManifest.xml 来为全部系统组件或应用的权限申请权限,在需要获取权限的组件中加入 android:permission 属性并命名权限即可。

Activity 权限(<activity> 标签中) 限制了谁可以打开这个activity,权限会在 Context.startActivity() 和Activity.startActivityForResult()中进行检查,如果调用方法的一方没有打开这个activity的权限则会抛出 SecurityException 异常。

Service 权限( <service> 标签中) 限制了谁可以开启或者绑定这个服务,权限会在 .startService()Context.stopService() 和 Context.bindService() 中进行检查,如果调用方法的一方没有所需的权限则会抛出 SecurityException 异常。

BroadcastReceiver 权限<receiver> 标签中) 限制了谁可以发送这条广播,权限在 Context.sendBroadcast() 返回后检查,系统会向指定的 receiver  发送广播,处理的结果是,如果没有权限的话不会抛出异常,只是广播无法接收,同样,这个权限可以也可以让Context.registerReceiver() 来控制谁可以发送广播给这个 receiver,这个权限也可以在调用 Context.sendBroadcast() 时限制哪些 BroadcastReceiver可以收到这条广播(详细见下)。

ContentProvider 权限( <provider> 标签中) 限制了谁可以获取 ContentProvider 中的数据(对于 Content providers 的获取,系统提供了一个重要的安全功能,称作 URI permissions ,随后会详细描述) 不像其他组件,provider有两个分开的权限: android:readPermission 限制了谁可以读取数据而 android:writePermission 限制了谁可以写入数据,注意如果一个 provider 同时被读和写的权限所保护,那么仅仅获取了写的权限不代表我们也同时有了读的权限。权限会在我们第一次获取 provider 时检查(如果读写权限都没有会抛出异常),在操作provader时,使用 ContentResolver.query() 方法需要读的权限,而使用ContentResolver.insert()、 ContentResolver.update()、 ContentResolver.delete() 这些方法需要写的权限,如果不具备所对应的权限就会抛出 SecurityException 异常。

发送广播时获取权限

除了在注册广播可以声明谁可以发送广播给这个 BroadcastReceiver 的权限(如上所述),我们也可以在发送广播时指定申请所需要的权限,通过调用 Context.sendBroadcast() 方法并附带权限的 string,我们可以指定必须具有这个权限的应用才可以收到这条广播。

注意 receiver 和 broadcaster 都可以申请权限,当遇到这种情况时,两个权限都必须检查通过才能使广播可以正常发送和接收。

其他获取权限的方式

对任何进入一个服务的方法调用都可以强制检查其权限,这由 Context.checkCallingPermission()方法完成,方法调用会附带需要的权限 string 且会返回一个 integer 表明权限是否被授予给当前的应用进程,注意这仅仅在收到了来自其他进程的调用才会去检查,通常是其他进程通过服务的公共 IDL 接口或者其他方式。

有很多有用的方法来检查权限,如果你有其他进程的 pid ,你可以使用 Context.checkPermission(String, int, int) 来检查那个进程没有的权限,如果有其他进程的包名,则可以用  PackageManager.checkPermission(String, String) 来找出进程是否被授予了某个特定的权限。

URI 权限


在使用 content provider 时,并不能只用标准的系统权限,当Content provider的客户端需要将指定的URI传给其他应用并让其他应用操作时,它需要读和写的权限来保护自身的数据。 邮件应用中的附件就是一个典型的列子,获取邮件应该被权限保护,因为这是用户的敏感数据,然而,如果某个图片查看器获得了图片附件的URI,那么除非这个图片查看器获得了获取所有邮件的权限,否则就不能打开这个附件。

这个问题的解决方案是 per-URI 权限:当开启一个 activity 或者返回一个结果给 activity,调用方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION 和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION ,这给activity授予了获取intent中指定的数据URI的权限,无论其是否拥有获取这个intent相关的 content provider 的数据的权限。

这个权限可以让与用户交互时 (打开附件、选择一个联系人) 专门授予一个对应的更细的权限, 这相当于一个灵活的钥匙,减少了应用对只和应用行为相关的权限的需求。

然而,更加细化的 URI 权限的授予,需要持有这些URI的 content provider 的合作, 强烈建议 content provider 通过声明android:grantUriPermissions 属性或<grant-uri-permissions> 标签来实现这个功能。

如需了解更多请查阅 Context.grantUriPermission()Context.revokeUriPermission()、 Context.checkUriPermission()方法。


0 0