Android 是一个权限分离的操作系统,在这个系统里,每一个应用程序都拥有独特的系统身份(User ID 或者 Group ID)。甚至系统的某些部分也被赋予了不同的 ID。因此,Linux 将每个应用程序和其他应用程序之间隔离开来。

Android 通过“权限”机制实现了很好的安全特性,在这个权限之下,你仅可以访问特定文件,因为在访问文件的时候都加了限制条件,你只有在满足条件的下情况才可以访问某些文件。

这篇文章向你讲述了如何使用 Android 系统的这些安全特性。

1. Security Architecture(安全体系结构)

Android 安全体系设计的核心是:默认情况下,任何应用程序都不能执行一些对其他应用程序、操作系统或者用户带来不利的操作。例如:读、写用户的隐私信息,读、写其他应用程序的文件,私自访问网络等等。

每个应用程序都运行在自己独自的安全沙箱里,所以,如果想要和其他应用程序共享资源,就必须“显式”的声明一些权限,因为默认的沙箱是没有这些权限的。当应用程序在清单文件中声明了一些特定的权限之后,在应用程序安装的时候,系统会提示用户同意这些权限。

应用程序的沙箱技术并不依赖于 Android 系统构建应用程序的技术。因为 Dalvik VM 并不是安全的,任何应用程序都可以在上面运行 Native Code。各种类型的应用程序(Java、Native、Hybrid)相互之间拥有相同的安全等级。

2. Application Signing

所有 Android 系统中的应用程序都必须指定一个签名文件,这个文件的用户名和密码是由这个应用程序的开发者指定的。这个签名文件的作用是:帮助系统识别应用程序的作者。这个签名文件并不需要特殊的签名机构来创建,任何一个开发者都可以创建。当新的应用程序需要安装的时候,系统就会根据签名文件识别两个应用程序是否可以分配同一个 Linux User ID。

3. User IDs and File Access

在安装应用程序的时候,系统会为每一个应用程序指定一个 Linux User ID。这个 User ID 将会一直保存到应用程序卸载为止。在不同的设备上,系统将会为同一个应用程序分配不同的 User ID。

因为“安全机制”的执行总是发生在“进程”级别,所以拥有不同包名的应用程序不能运行在相同的进程中,因为它们是不同的用户。但是你可以通过设置shareUserId属性为不同的应用程序设置相同的 Linux User ID。这样做之后,系统便会把这两个应用程序当成一个,使它们具有相同的 User ID 和相同的文件访问权限。但是为了安全起见,只有拥有相同签名文件和相同sharedUserId的两个应用程序才拥有相同的 User ID。

应用程序存储的任何数据都将被赋予当前应用程序的 User ID 属性,并且只有 User ID 匹配的应用程序才可以访问指定文件。当你调用 getSharedPreferences(String, int)openFileOutput(String, int)或者openOrCreateDatabse(String, int, SQLiteDatabase.CursorFactory)函数保存文件时,你可以为文件指定MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE标志,这样之后,其他应用程序就可以读、写你应用程序创建的文件。也就是说,当你设置了这些属性之后,虽然文件还是你应用程序创建的,但是其他应用程序也可以对它进行读、写操作。

4. Using Permissions

默认情况下,Android 应用程序是不能做任何不利于用户体验或影响设备数据的操作。为了使用这些权限,你必须在清单文件里面声明应用程序所需的权限。

例如,一个应用程序需要监听“收到短消息”需要这样声明:

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

系统在安装应用程序的时候就会提示用户“允许”或者“否定”当前应用程序所需的权限。在程序运行的时候,系统是不会检查这些权限的;如果在安装的时候,用户已经同意了某些权限,那么在运行时,它就可以利用这些特性;反之则不行。

通常情况下,如果应用程序的某个权限是“不允许”的,那么就会抛出SecurityException。但是无法保证着会在任何地方都会发生。例如:当你调用sendBroadcast(Intent)函数检查数据传送时每个接收者是否拥有接受数据的权限时,在函数执行完之后,即使接受这没有这个权限,但是依然不会报异常。但是在大多数情况下,当程序缺少某些权限时,系统将会在 LogCat 中将日志输出。

在一个正常的情况下(例如:用户从 Google Play 下载并安装了你的应用程序),如果用户不同意你应用程序的所需的权限,那么应用程序是无法在该设备上安装的。因此,通常情况下,你并不需要担心由于“丢失权限”而引起的“运行时错误”,也就是说,只要一个设备安装了你的应用程序,那么它就允许了你应用程序所需的任何权限。

Android 系统提供的应用程序权限可以在Manifest.permission文档中找到。但是任何一个都可以定义并且执行自定义的权限,因此上面的列表并不是一个真正意义上“完整”的权限列表。

一个特定的权限可能在很多情况下执行,例如:

  1. 当有电话打入的时候,阻止应用程序的一些功能(有来电时,音乐播放器将被强制停止);

  2. 当启动过一个 Activity 时,阻止应用程序启动其他应用的 Activity;

  3. 当发送或者接收广播时,控制 Who 可以接收你的广播、 Who 可以向你的应用程序发送广播。

  4. 当访问和操作 Content Provider 时;

  5. 当 Binding 或者 Starting 一个 Service 时。

注意:随着时间的推移,新的“限制”将会添加在新的 API 中,因此,为了使你的应用程序可以正常的工作,你必须在应用程序里面声明一些先前不需要的权限。因为已经发布的应用程序假设某些新添加的权限在当前版本是可用的,因此 Android 系统将会默认这些应用拥有这些新添加的权限。但是 Android 系统这样做是有前提条件的:如果 Android 应用程序的 targetSdkVersion 比添加新权限的版本低,那么 Android 系统将会为当前应用添加这些权限。

例如:WRITE_EXTERNAL_STORAGE权限是在 API level 4 时添加。如果你的应用程序的 targetSdkVersion 是 3 或者更低,那么 Android 系统默认将会为此应用加上这个权限。

如果上面的情况恰巧发生在你的应用程序上,那么 Google Play 将会在应用商店里列出你应用程序所需的权限(在新的 API 中添加的),即使你的应用程序不需要。

为了避免上面的情况发生,所以你需要将应用程序 targetSdkVersion 尽可能的设置高些。

5. Declaring and Enforcing Permissions

为了执行自定义的权限,你需要在清单文件里用 <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>属性是可选的,它的主要作用是:向用户展示一些提示信息,通常情况下,你应该使用标准的 standard system group,当然,你也可以使用自定义的权限,但是,如果你使用的是系统预定义的权限,提示的 UI 将会更加友好,言外之意就是:使用系统的权限组吧,自定义的权限组还不知道提示成什么样子呢。

注意: labeldescription 属性应该在声明权限的时候加上,label 标签的主要作用是在权限列表当中显示主要信息,description 标签的主要作用是描述每一个权限的具体信息。通常情况下,label 标签应该是非常简短的,description 标签应该是由两句话构成的。

下面是一个关于 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>

你可以在设备的“设置”应用中查看当前应用的权限,也可以通过命令行查看,但是效果都是一样的(下面是某一个应用程序的权限列表):

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

1.Enforcing Permissions in AndroidManifest.xml

High-level的权限可以限制其他应用程序访问你应用程序的组件,但是你需要在清单文件中声明这些权限。

Activity 权限可以限制访问 Activity。当有 User 调用 Context.startActivity() 或者 Activity.startActivityForResult() 函数时,系统便会检查当前调用者是否拥有此权限,如果调用者并没有这个权限,那么将会抛出一个 SecurityException 异常。

Service 权限可以限制启动或者绑定 Service。当有 User 调用 Context.startService()Context.stopService() 或者 Context.bindService() 函数时,系统便会检查当前调用者是否拥有此权限,如果调用者并没有这个权限,那么将会抛出一个 SecurityException 异常。

BroadcastReceiver 权限可以限制 Who 可以向指定的 receiver 发送广播。系统将会在 Context.sendBroadcast()函数返回之后,也就是在系统将广播发送给指定接受者时。但是如果发送者没有这个权限,并不会抛出异常,只是不发送这个 “Intent”而已。同样的,上面的权限也可以应用在“动态”注册广播的情况。

ContentProvider 权限可以限制访问 ContentProvider 里面的数据。不像其他三个组件, ContentProvider 拥有两个权限属性: android:readPermissionandroid:writePermissionandroid:readPermission 主要作用是限制读取 ContentProvider 里面的内容,android:writePermission 主要作用是限制向 ContentProvider 里面写入内容。当你第一次获取 ContentProvider 对象或者在 ContentProvider 对象上执行操作时,系统就会检查你是否拥有上面两个权限之一,如果没有就会抛出一个异常信息。当你调用 ContentResolver.query() 函数时,需要读权限,当你调用 ContentResolver.insert(), ContentResolver.update(), ContentResolver.delete()函数时,需要写权限,如果没有相应的权限系统将会抛出异常信息。

2. Enforcing Permissions when Sending Broadcasts

除了上面讲述的“限制向指定接受者发送广播”之外,你还可以在发送广播时添加一些权限,从而使只有拥有相关权限的接收者才可以接收这个广播。

注意:接受者和发送者可以同时设定一个权限,当上面的情况发生时,只有同时满足上面两个条件,这个 “Intent”才能正常的发送和接收。

3. Other Permission Enforcement

任何权限都可以在调用的时候强制执行,你只需调用 Context.checkCallingPermission() 函数,并在调用的时候传入相应权限的名称即可,然后系统就会返回一个整型值,这个值代表了:指定权限是否在当前进程中被“允许”。注意,这个方法只适合在不同的进程中调用,通常情况下,是通过 IDL 接口或者其他方式实现的。

当然,Android 系统还提供了一些其他的方法用于检查权限。如果你有其他进程的 PID,你可以通过调用 Context 的 checkPermission(String, int, int) 函数检查。如果你有其他应用的包名,你可以通过调用 PackageManager 的 PackageManager.checkPermission(String, String) 函数去检查。

6. URI Permissions

当你在使用 ContentProvider 时,你会发现上面讲述的标准的权限系统是不够完整的。一个 Content Provider 可以拥有读、写权限,但是它的直接用户同时也需要一些权限。例如:Mail 应用程序中的附件。访问邮件时,会有相应的权限限制,因为它属于用户的隐私信息。但是如果邮件的附件被指向了图片查看器,此时图片查看其便没有查看附件的权限,因为图片查看器没有访问邮件的权限。

解决上述问题的办法是 – per-URI 权限:当启动一个 Activity 或者 向一个 Activity 返回结果时,调用者可以设置 Intent.FLAG_GRANT_READ_URI_PERMISSION 或者 Intent.FLAG_GRANT_WRITE_URI_PERMISSION 属性。这样之后,”receiving activity“就拥有了访问返回 Intent 中指定数据 URI 的权限,无论”receiving activity“是否拥有访问指定 ContentProvider 的权限。

这种机制提供了一个很好的模型 – 在用户和一些数据的交互的时候,可以拥有访问指定文件的权限。同时还减少了一些和应用操作相关的权限。

上面的权限模型需要和 Content Provider 结合使用才能发挥更大的作用。因此为了使 Content Provider 拥有上述的特性,特别建议在 Content Provider 里面实现这个能力,并通过 android:grantUriPermissions 属性进行显式的声明它支持这个特性。

好了,这篇文章到这里就结束了,希望可以帮到小伙伴,have a good day~

更多相关文章

  1. 关于Android程序入口的猜想
  2. Android(安卓)Intent机制实例详解(1)
  3. Android(安卓)系统(102)---Android(安卓)APP耗电优化
  4. android中通过代码实现文件权限修改(chmod)
  5. 调用Android自带日历功能
  6. Android系统下如何通过外设实现GPIO中断触发调用Android程序执行
  7. Android学习:进程以及优先级
  8. 使用EasyPermissions高效处理权限
  9. android构建系统总览

随机推荐

  1. nvm管理node是真的香
  2. 仿淘宝移动端首页
  3. JS原生输入框变化监听事件
  4. Docusaurus 一键快速部署个人博客
  5. Android(安卓)使用Okhttp进行文件下载
  6. android:EditText属性
  7. android webview 使用以及一些异常处理及
  8. Android(安卓)下移植WIFI 驱动
  9. Android(安卓)之EditText自动弹出/不弹出
  10. Android(安卓)保存图片到相册无法显示的