原文:
zh.annas-archive.org/md5/88CB58B22F96FE00C43EA554A1F58FCE
译者:飞龙
前言
在当今技术发达的世界,我们的生活越来越多地数字化,所有这些信息都可以通过移动设备随时随地访问。用户可以下载和尝试成千上万的应用程序。由于移动设备上的应用程序可以轻松访问大量信息,最大的挑战就是保护用户的私人信息并尊重他们的隐私。
第一款 Android 手机在 2009 年问世。从那时起,移动生态系统就不再一样了。平台的开放性和较少限制的应用模型在开发人员社区中引起了兴奋,也促进了创新和实验。但正如每个硬币都有两面一样,开放性也是如此。Android 平台激发了所谓不良分子的想象力。Android 为他们提供了一个完美的试验场来尝试他们的想法。因此,作为开发人员,甚至作为消费者,了解 Android 的安全模型以及如何明智地使用它来保护自己和消费者,这一点非常重要。
《Android 应用安全基础》深入探讨了从内核层到应用层的 Android 安全,包含实用的实战示例、图解和日常使用场景。这本书将向你展示如何保护你的 Android 应用和数据。它将为你提供在开发应用时非常有用的技巧和提示。
你将学习 Android 堆栈的整体安全架构。书中详细讨论了使用权限保护组件、在清单文件中定义安全、加密算法和协议、安全存储、以安全为重点的测试以及保护设备上的企业数据。你还将学习在将新技术和用例(如 NFC 和移动支付)集成到你的 Android 应用程序时如何保持安全意识。
本书涵盖的内容
第一章,Android 安全模型 - 大局观,关注从平台安全到应用安全的整个 Android 堆栈的安全性。这一章将为后续章节的构建提供基准。
第二章,应用构建块,从安全的角度介绍了应用组件、权限、清单文件和应用程序签名。这些都是 Android 应用程序的基本组成部分,了解这些组件对于构建我们的安全知识至关重要。
第三章,权限,讨论了 Android 平台现有的权限,如何定义新权限,如何用权限保护应用组件,并提供关于何时定义新权限的分析。
第四章,定义应用程序的策略文件,深入探讨了 manifest 文件的工作机制,这是应用程序的策略文件。我们讨论了如何加强策略文件的技巧和小窍门。
第五章,尊重你的用户,涵盖了妥善处理用户数据的最佳实践。这对于开发者的声誉至关重要,因为声誉取决于用户的评价和评分。开发者还应该小心处理用户的私人信息,以免陷入法律陷阱。
第六章,你的工具 - 加密 API,讨论了 Android 平台提供的加密能力。这些包括对称加密、非对称加密、散列、密码模式和密钥管理。
第七章,保护应用程序数据,全面介绍了应用程序数据在静态和传输过程中的安全存储。我们讨论了如何将私密数据与应用程序一起沙盒化,以及如何在设备上、外部存储卡、驱动器和数据库上安全地存储数据。
第八章,企业中的 Android,讨论了 Android 平台提供的设备安全工具有及其对应用程序开发人员意味着什么。这一章对企业应用程序开发者特别有吸引力。
第九章,安全测试,专注于设计和开发以安全为重点的测试用例。
第十章,未来展望,讨论了移动领域即将到来的用例,以及这对 Android 的影响,尤其是从安全角度出发。
阅读本书所需的条件
如果你能设置好 Android 环境,并且能够实践本书中讨论的概念和示例,那么这本书将更有价值。关于如何设置环境和开始 Android 开发的详细说明,请参考developer.android.com。如果你对内核开发感兴趣,请参考source.android.com。
在撰写本书时,Jelly Bean(Android 4.2,API 级别 17)是最新版本。我在这个平台上测试了所有的代码片段。自从 2009 年 Cupcake 的首个版本发布以来,谷歌一直在通过连续的版本更新来增强 Android 的安全性。例如,在 Android 2.2(API 级别 8)中添加了远程擦除和设备管理 API,使 Android 对商业社区更具吸引力。每当相关时,我都会引用开始支持特定功能的版本。
本书适合的读者对象
这本书是任何对移动安全感兴趣的人的绝佳资源。开发人员、测试工程师、工程经理、产品经理和架构师可以在设计和编写应用程序时将这本书作为参考。高级管理人员和技术专家可以利用这本书来更全面地了解移动安全。最好具备一些关于 Android 堆栈开发的先验知识,但不是必需的。
约定
在这本书中,您会发现多种文本样式,这些样式区分了不同类型的信息。以下是一些样式示例,以及它们的含义解释。
文本中的代码字如下所示:“The PackageManager
类处理安装和卸载应用程序的任务。”
代码块设置如下:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
当我们希望引起您对代码块中特定部分的注意时,相关的行或项目会以粗体显示:
Intent intent = new Intent(“my-local-broadcast”);
Intent.putExtra(“message”, “Hello World!”);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
命令行输入或输出如下所示:
dexdump –d –f –h data@app@com.example.example1-1.apk@classes .dex > dump
新术语和重要词汇以粗体显示。您在屏幕上看到的词,例如菜单或对话框中的,会像这样出现在文本中:“点击下一步按钮将带您进入下一个屏幕”。
注意
警告或重要注意事项会出现在这样的框中。
提示
技巧和诀窍如下所示。
读者反馈
我们始终欢迎读者的反馈。让我们知道您对这本书的看法——您喜欢或可能不喜欢的内容。读者的反馈对我们来说非常重要,以便开发出您真正能从中获得最大收益的标题。
要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>
,并在邮件的主题中提及书名。
如果您在某个主题上有专业知识,并且有兴趣撰写或参与书籍编写,请查看我们在www.packtpub.com/authors上的作者指南。
客户支持
既然您已经拥有了 Packt 的一本书,我们有许多方法可以帮助您充分利用您的这次购买。
勘误
尽管我们已经竭尽全力确保内容的准确性,但错误仍然会发生。如果您在我们的书中发现错误——可能是文本或代码中的错误——如果您能报告给我们,我们将不胜感激。这样做可以避免其他读者感到沮丧,并帮助我们在后续版本中改进这本书。如果您发现任何勘误信息,请通过访问www.packtpub.com/submit-errata
,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情。一旦您的勘误信息得到验证,您的提交将被接受,勘误信息将被上传到我们的网站,或添加到该标题勘误部分的现有勘误列表中。任何现有的勘误信息可以通过选择您的标题从www.packtpub.com/support
进行查看。
侵权行为
在互联网上,版权材料的侵权行为是所有媒体持续存在的问题。在 Packt,我们非常重视保护我们的版权和许可。如果您在互联网上以任何形式遇到我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
如果您发现疑似盗版材料,请通过<copyright@packtpub.com>
联系我们,并提供相关链接。
我们感谢您帮助保护我们的作者,以及我们向您提供有价值内容的能力。
问题咨询
如果您在书的任何方面遇到问题,可以通过<questions@packtpub.com>
联系我们,我们将尽力解决。
第一章:安卓安全模型 – 大局观
欢迎阅读《安卓应用安全基础》的第一章!
安卓系统在许多方面与众不同。它是开放的,比其他一些平台更先进,并汲取了之前开发移动平台的经验教训。在本章中,我们将从内核到应用层,介绍安卓安全模型的基础知识。本章中引入的每个安全工件事后都会在后续章节中进行更详细的讨论。
我们从解释为什么安装时应用程序权限评估对安卓平台和用户数据的安全至关重要开始本章。安卓具有分层架构,本章将讨论每个架构层的安全评估。我们以讨论核心安全工件事物作为本章的结束,如应用程序签名、设备上的安全数据存储、加密 API 以及安卓设备的管理等。
谨慎安装
安卓与其他移动操作系统不同的一个特点是应用安装时的权限审查。应用程序所需的所有权限都必须在其清单文件中声明。这些权限是应用程序正常运行所需的权限,例如访问用户的联系人列表、从手机发送短信、拨打电话以及访问互联网等。详细描述这些权限的内容请参考第三章,权限。
当用户安装应用程序时,清单文件中声明的所有权限都会呈现给用户。用户可以审查这些权限,并做出是否安装应用的明智决定。用户应仔细审查这些权限,因为这是唯一一次要求用户授权。这一步骤之后,用户对应用程序的控制就极为有限,最多只能卸载应用。有关参考,请查看以下屏幕截图。在这个例子中,应用程序将追踪或访问用户位置,使用网络,读取用户联系人列表,读取手机状态,并使用某些开发能力。在安全审查此应用程序时,用户必须评估授予该应用程序某些权限是否必要。如果这是一个游戏应用,可能不需要开发工具能力。如果这是一个面向儿童的教育应用,它不应该需要访问联系人列表或用户位置。还要注意,开发者可以添加自己的权限,特别是如果他们想要与可能在设备上安装的其他自己开发的应用程序进行通信时。开发者有责任为这类权限提供清晰的描述。
在安装时,框架会确保应用程序中使用的所有权限都在清单文件中声明。操作系统在运行时会强制执行这些权限。
Android 平台架构
Android 是一个具有分层软件堆栈的现代操作系统。下图说明了 Android 软件堆栈中的层。此软件堆栈运行在设备硬件之上。Android 的软件堆栈可以运行在许多不同的硬件配置上,如智能手机、平板电脑、电视,甚至嵌入式设备如微波炉、冰箱、手表和笔。每一层都提供了安全性,为移动应用程序提供了一个安全运行和执行的环境。在本节中,我们将讨论 Android 堆栈每一层提供的安全保障。
Linux 内核
设备硬件之上是Linux 内核。Linux 内核已经被用作一个安全的多用户操作系统数十年,将一个用户与另一个用户隔离开来。Android 使用 Linux 的此属性作为 Android 安全的基础。将 Android 想象成一个多用户平台,每个用户都是一个应用程序,每个应用程序都与其他应用程序隔离。Linux 内核托管设备驱动程序,如蓝牙、摄像头、Wi-Fi 和闪存驱动程序。内核还提供了一种安全的远程过程调用(RPC)机制。
每个应用程序在设备上安装时,都会被赋予唯一的用户识别码(UID)和组识别码(GID)。只要应用程序在设备上安装,这个 UID 就是应用程序的身份标识。
请参考以下屏幕截图。第一列列出了所有应用程序的 UID。请注意突出的应用程序。应用程序com.paypal.com
具有 UID app_8
,而com.skype.com
具有 UID app_64
。在 Linux 内核中,这两个应用程序都以其自己的进程运行,并使用这个 ID。
请参考下一张屏幕截图。当我们在壳层中输入id
命令时,内核会显示 UID、GID 以及与壳层关联的组。这是 Android 用来将一个进程与另一个进程隔离的进程沙箱模型。两个进程可以相互共享数据。如何正确进行数据共享的机制将在第四章,定义应用程序的策略文件中进行讨论。
尽管大多数 Android 应用程序是用 Java 编写的,但有时需要编写本地应用程序。本地应用程序更为复杂,因为开发者需要管理内存和特定于设备的问题。开发者可以使用 Android NDK 工具集来用 C/C++开发应用程序的部分。所有本地应用程序都符合 Linux 进程沙箱化;本地应用程序和 Java 应用程序的安全性没有区别。请记住,与任何 Java 应用程序一样,需要适当的加密、散列和安全的通信等安全构件。
中间件
在 Linux 内核之上是提供代码执行库的中间件。这类库的例子有libSSL
、libc
、OpenGL
。这一层还提供了 Java 应用程序的运行时环境。
由于大多数用户在 Android 上用 Java 编写应用程序,一个显而易见的问题是:Android 是否提供了一个Java 虚拟机?这个问题的答案是:不,Android 没有提供 Java 虚拟机。因此,Java 归档文件(JAR)在 Android 上无法执行,因为 Android 不执行字节码。Android 提供的是Dalvik 虚拟机。Android 使用一个名为dx
的工具将字节码转换为Dalvik 可执行文件(DEX)。
Dalvik 虚拟机
Dalvik 虚拟机最初由 Dan Bornstein 开发,他以冰岛的一个渔村 Dalvik 命名,那里住着他的部分祖先。Dalvik 是一个基于寄存器、高度优化、开源的虚拟机。它与 Java SE 或 Java ME 并不一致,其库基于Apache Harmony。
每个 Java 应用程序都在其自己的虚拟机中运行。当设备启动时,一个名为Zygote的原始进程会生成一个虚拟机进程。这个 Zygote 进程随后根据请求分叉以创建新的虚拟机进程。
Dalvik 背后的主要动机是通过增加共享来减少内存占用。因此,Dalvik 中的常量池是一个共享池。它还在不同的虚拟机进程之间共享核心的只读库。
Dalvik 依赖 Linux 平台提供所有底层功能,如线程和内存管理。Dalvik 为每个虚拟机设置了独立的垃圾收集器,但会处理共享资源的进程。
Dan Bornstein 在 2008 年的 Google IO 上关于 Dalvik 做了精彩的演讲。你可以在www.youtube.com/watch?v=ptjedOZEXPM
找到它。去看看吧!
应用层
开发基于 Java 的应用程序的应用程序开发者与 Android 堆栈的应用层交互。除非你正在创建一个本地应用程序,否则这一层将为你提供创建应用程序所需的所有资源。
我们可以将应用层进一步划分为应用框架层和应用层。应用框架层提供了由 Android 堆栈公开供应用程序使用的类。例如,活动管理器管理活动的生命周期,包管理器管理应用程序的安装和卸载,通知管理器向用户发送通知。
应用层是应用程序所在的层次。这些可以是系统应用程序或用户应用程序。系统应用程序是设备预装的应用程序,如邮件、日历、联系人和浏览器。用户无法卸载这些应用程序。用户应用程序是用户在设备上安装的第三方应用程序。用户可以根据自己的意愿自由安装和卸载这些应用程序。
Android 应用程序结构
要理解应用层的安全性,重要的是要了解 Android 应用程序的结构。每个 Android 应用程序都被创建为一堆组件的栈。这种应用程序结构的美妙之处在于,每个组件都是自包含的实体,甚至可以被其他应用程序独立调用。这种应用程序结构鼓励组件共享。下图展示了 Android 应用程序的组成,包括活动、服务、广播接收器和内容提供者:
Android 支持四种类型的组件:
-
活动:这个组件通常是应用程序的用户界面部分。这是与用户交互的组件。活动组件的一个例子是登录页面,用户在该页面输入用户名和密码以对服务器进行身份验证。
-
服务:这个组件负责在后台运行的过程。服务组件没有 UI。一个例子是,一个与音乐播放器同步并在用户预先选择的情况下播放歌曲的组件。
-
广播接收器:这个组件是接收来自 Android 系统或其他应用程序消息的邮箱。例如,Android 系统在启动后会触发一个名为
BOOT_COMPLETED
的 Intent。应用程序组件可以在清单文件中注册,以便监听此广播。 -
内容提供者:这个组件是应用程序的数据存储。应用程序还可以将此数据与其他 Android 系统的组件共享。内容提供者组件的一个示例用例是,一个应用程序存储了用户为购物保存的物品列表。
所有上述组件都在AndroidManifest.xml
(清单)文件中声明。除了组件,清单文件还列出了应用的其他需求,如 Android 所需的最低 API 级别,应用所需的用户权限,例如访问互联网和读取联系人列表,应用使用硬件的权限,如蓝牙和相机,以及应用链接到的库,如 Google Maps API。第四章,定义应用的策略文件,将更详细地讨论清单文件。
活动、服务、内容提供者和广播接收器都通过意图(Intents)相互通信。意图是 Android 的异步进程间通信(IPC)机制。组件通过发射意图来执行一个动作,接收组件根据意图采取行动。有单独的机制用于将意图传递给每种类型的组件,因此活动意图只传递给活动,广播意图只传递给广播接收器。意图还包括一个名为Intent
对象的信息包,接收组件使用它来采取适当的行动。重要的是要了解意图并不安全。任何监听应用都可以嗅探意图,因此不要在其中放置任何敏感信息!并且想象一下,如果意图不仅被嗅探,还被恶意应用篡改的场景。
例如,下图展示了两个应用,应用 A和应用 B,它们各自有一组组件。只要它们有权限,这些组件就可以相互通信。应用 A中的活动组件可以使用startActivity()
启动应用 B中的活动组件,也可以使用startService()
启动自己的服务。
在应用级别,Android 组件遵循基于权限的模型。这意味着组件必须拥有适当的权限才能调用其他组件。尽管 Android 提供了应用可能需要的绝大多数权限,但开发者仍有能力扩展此模型。但这种情形应很少使用。
额外的资源,如位图、UI 布局、字符串等,在另一个不同的目录中独立维护。为了提供最佳的用户体验,这些资源应当针对不同的地区进行本地化处理,并针对不同的设备配置进行定制。
接下来的三章将详细讨论应用结构、清单文件和权限模型。
应用签名
Android 的一个区别因素是 Android 应用程序的签名方式。Android 中的所有应用程序都是自签名的。不需要使用证书颁发机构对应用程序进行签名。这与传统的应用程序签名不同,后者通过签名识别作者,并基于签名建立信任。
应用程序的签名将应用程序与作者关联起来。如果用户安装了同一作者编写的多个应用程序,且这些应用程序想要共享彼此的数据,它们需要具有相同的签名,并且在清单文件中设置了SHARED_ID
标志。
应用程序签名在应用程序升级时也会被使用。应用程序升级要求两个应用程序具有相同的签名,且没有权限提升。这是 Android 中确保应用程序安全的另一机制。
作为应用程序开发者,保持用于签名应用程序的私钥安全是非常重要的。作为应用程序作者,你的声誉取决于它。
设备上的数据存储
Android 提供了不同的解决方案,用于设备上的安全数据存储。基于数据类型和应用程序用例,开发者可以选择最适合的解决方案。
对于需要在用户会话之间持久化的基本数据类型,如整数(ints)、布尔值(booleans)、长整数(longs)、浮点数(floats)和字符串(strings),最好使用共享数据类型。共享偏好设置中的数据以键值对的形式存储,允许开发者保存
、检索
和持久化
数据。
所有应用程序数据都与应用程序一起存储在沙盒中。这意味着这些数据只能由该应用程序或具有相同签名且被授权共享数据的其他应用程序访问。最好将私有数据文件存储在此内存中。这些文件将在应用程序被卸载时被删除。
对于大型数据集,开发者可以选择使用 Android 软件堆中捆绑的 SQLite 数据库。
所有 Android 设备允许用户挂载外部存储设备,如 SD 卡。开发者可以编写应用程序,以便将这些大文件存储在这些外部设备上。这些外部存储设备大多数采用 VFAT 文件系统,Linux 访问控制在这里不起作用。敏感数据在存储在这些外部设备上之前应进行加密。
从 Android 2.2(API 8)开始,APK 可以存储在外部设备上。使用随机生成的密钥,APK 被存储在一个名为asec
文件的加密容器中。这个密钥存储在设备上。Android 上的外部设备是以noexec
方式挂载的。所有的 DEX 文件、私有数据和本地共享库仍然存放在内部存储中。
在任何可以连接网络的地方,开发者可以将数据存储在自己的网络服务器上。建议将可能泄露用户隐私的数据存储在自家服务器上。例如,银行应用程序应将用户账户信息和交易细节存储在服务器上,而不是用户设备上。
第七章,保护应用程序数据,详细讨论了 Android 设备上的数据存储选项。
如视频、电子书和音乐等权利受保护的内容,可以使用 DRM 框架 API 在 Android 上得到保护。应用程序开发者可以使用此 DRM 框架 API 将设备注册到 DRM 方案,获取与内容相关的许可证,提取约束,并将相关内容与其许可证关联。
加密 API
Android 拥有一套全面的加密 API 套件,应用开发者可以使用它来保护数据,无论是在静止状态还是在传输过程中。
Android 提供了用于数据对称和不对称加密、随机数生成、散列、消息认证码和不同密码模式的 API。支持的算法包括 DH、DES、三重 DES、RC2 和 RC5。
安全通信协议如 SSL 和 TLS,结合加密 API,可用于保护传输中的数据。还提供了包括 X.509 证书管理的密钥管理 API。
自 Android 1.6 以来,系统密钥存储已被用于 VPN。Android 4.0 引入了一个名为KeyChain
的新 API,允许应用程序访问存储在那里的凭据。此 API 还支持从 X.509 证书和 PKCS#12 密钥存储中安装凭据。一旦应用程序被授予访问证书的权限,它就可以访问与证书关联的私钥。
加密 API 在第六章,你的工具 – 加密 API中进行了详细讨论。
设备管理
随着移动设备在工作场所的普及,Android 2.2 引入了设备管理 API,允许用户和 IT 专业人士管理访问企业数据的设备。使用此 API,IT 专业人士可以在设备上实施系统级安全策略,如远程擦除,密码启用及具体设置。Android 3.0 和 Android 4.0 进一步增强了此 API,增加了密码过期、密码限制、设备加密要求和禁用摄像头等策略。如果你有邮件客户端并在 Android 手机上用它来访问公司邮件,那么你很可能正在使用设备管理 API。
设备管理 API 通过强制执行安全策略来工作。DevicePolicyManager
列出了设备管理员可以在设备上执行的所有策略。
设备管理员编写一个应用程序,用户在其设备上安装该程序。安装后,用户需要激活策略以在设备上实施安全策略。如果用户未安装该应用,则不适用安全策略,但用户也无法访问该应用提供的任何功能。如果设备上有多个设备管理应用程序,则最严格的策略优先。如果用户卸载该应用,则策略会被停用。在卸载时,根据其拥有的权限,应用程序可能会决定将手机重置为出厂设置或删除数据。
我们将在第八章,企业中的 Android中更详细地讨论设备管理。
总结
安卓是一个现代操作系统,安全性已经内置于平台中。正如我们在本章中学到的,具有进程隔离特性的 Linux 内核构成了安卓安全模型的基础。每个应用程序及其应用数据都与其它进程隔离。在应用程序层面,组件通过意图相互交流,并且需要具备适当的权限才能调用其他组件。这些权限在久经考验的安全多用户操作系统 Linux 内核中得到执行。开发者拥有一套全面的加密 API,用以保护用户数据。
有了对安卓平台的基本了解,让我们迈向下一章,从安全角度理解应用程序组件和组件间通信。祝您好运!
第二章:应用程序构建块
本章节关注 Android 应用程序的构建块,即应用程序组件和组件间通信。Android 系统中有四种类型的组件:活动(Activities)、服务(Services)、广播接收器(Broadcast Receivers)和内容提供器(Content Providers)。每个组件都特别设计用来完成一个特定任务。这些组件的集合构成了一个 Android 应用程序。这些组件通过 Intents 进行相互通信,Intents 是 Android 的跨进程通信机制。
有几本书讨论了如何构建 Android 组件和 Intents。实际上,Android 开发者网站在介绍使用这些组件进行编程方面也做得相当不错。因此,在本章中,我们不是要涵盖实施细节,而是要讨论每个组件的安全方面,以及如何在应用程序中定义和使用组件及 Intents,以保护我们作为开发者的声誉和消费者的隐私。
本章节的重点是组件和 Intents。对于每个 Android 组件,我们将涵盖组件声明、与组件相关的权限以及特定于该组件的其他安全考虑。我们将讨论不同类型的 Intents 以及在不同情境下最佳 Intents 的使用。
应用程序组件
正如我们在第一章简要提及的,Android 安全模型 - 大蓝图,一个 Android 应用程序是由一系列松散绑定的应用组件组成的。应用组件、清单文件以及应用资源被打包在一个应用包格式 .apk
文件中。一个APK文件本质上是一个采用 JAR 文件格式的 ZIP 文件。Android 系统只识别 APK 格式,因此所有要安装在 Android 设备上的包都必须是 APK 格式。APK 文件随后使用开发者的签名进行签名,以确认作者身份。PackageManager
类负责安装和卸载应用程序的任务。
在本节中,我们将详细讨论每个组件的安全性。这包括在清单文件中声明组件,因此我们会梳理掉松散的结尾以及其他针对每个组件的独特安全考虑。
活动
活动是通常与用户交互的应用程序组件。活动扩展了 Activity
类,并作为视图和片段实现。片段是在Honeycomb中引入的,以解决不同屏幕尺寸的问题。在较小的屏幕上,一个片段显示为一个单一的活动,并允许用户导航到第二个活动以显示第二个片段。片段和活动启动的线程在活动的上下文中运行。因此,如果活动被销毁,与其相关的片段和线程也将被销毁。
一个应用程序可以有多个活动。最好使用一个活动专注于单一任务,并为各个任务创建不同的活动。例如,如果我们正在创建一个让用户在网站上订购书籍的应用程序,最好创建一个用于用户登录的活动,另一个用于在数据库中搜索书籍的活动,另一个用于输入订购信息,再一个用于输入支付信息,等等。这种风格鼓励在应用程序内以及设备上安装的其他应用程序中重用活动。组件的重用有两个主要好处。首先,它有助于减少错误,因为代码重复较少。其次,它使应用程序更安全,因为不同组件之间的数据共享较少。
活动声明
应用程序使用的任何活动都必须在AndroidManifest.xml
文件中声明。以下代码段显示了在清单文件中声明的登录活动和订单活动:
<activity android:label="@string/app_name" android:name=".LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".OrderActivity" android:permission="com.example.project.ORDER_BOOK" android:exported="false"/>
请注意,LoginActivity
被声明为一个公共活动,可以被系统中的任何其他活动启动。OrderActivity
被声明为一个私有活动(没有意图过滤器的活动是一个私有活动,只能通过指定其确切的文件名来调用)。此外,可以使用android:exported
标签指定它是否对应用程序外部可见。值为true
使活动在应用程序外部可见,值为false
则相反。本章稍后将讨论意图过滤器(Intent Filter)。
所有的活动都可以通过权限进行保护。在上述示例中,除了是私有的,OrderActivity
还受到权限com.example.project.ORDER_BOOK
的保护。任何尝试调用OrderActivity
的组件都应该具有此自定义权限才能调用它。
通常情况下,每当启动一个活动时,它都会运行在声明它的应用程序进程中。将android:multiprocess
属性设置为true
可以让活动运行在一个与应用程序不同的进程中。这些进程的具体设置可以通过使用android:process
属性来定义。如果此属性的值以冒号(:
)开头,将为应用程序创建一个新的私有进程;如果以小写字母开头,则活动将在全局进程中运行。
android:configChanges
标签允许应用程序处理由于列出的配置更改而重新启动活动。这样的更改包括本地化更改、插入外部键盘和 SIM 卡更改。
保存活动状态
系统通过活动栈管理所有活动(Activities)。当前与用户交互的活动在前台运行。当前活动可以启动其他活动。由于资源限制,处于后台的任何活动都可能被 Android 系统杀死。在配置更改(例如从垂直方向更改为水平方向)期间,活动也可能被重新启动。如前一部分所述,活动可以使用android:configChanges
标签自行处理其中一些事件。不建议这样做,因为它可能导致不一致。
在重新启动之前,应该保存活动(Activity)的状态。活动的生命周期由以下方法定义:
public class Activity extends ApplicationContext {
protected void onCreate(Bundle savedInstanceState);
protected void onStart();
protected void onRestart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestroy();
}
活动可以重写onSaveInstanceState(Bundle savedInstanceState)
和onRestoreInstanceState(Bundle savedInstanceState)
方法,以保存和恢复例如用户偏好和未保存文本的实例值。Android 开发者网站 www.developer.android.com
使用以下流程图精美地说明了这个过程:
下面的代码片段展示了活动(Activity)如何存储和检索首选语言、搜索结果数量和作者名称。当活动被销毁时,用户偏好作为Bundle存储,它存储名称-值对。当活动重新启动时,这个 Bundle 会被传递给onCreate
方法,该方法恢复了活动的状态。需要注意的是,这种存储方法不会在应用程序重新启动后保持。
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt("ResultsNum", 10);
savedInstanceState.putString("MyLanguage", "English");
savedInstanceState.putString("MyAuthor", "Thomas Hardy");
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
int ResultsNum = savedInstanceState.getInt("ResultsNum");
String MyLanguage = savedInstanceState.getString("MyLanguage");
String MyAuthor = savedInstanceState.getString("MyAuthor");
}
保存用户数据
如我们之前所讨论的,活动(Activities)与用户交互,因此它们可能会收集一些用户数据。这些数据可能是应用程序私有的,也可能是与其他人共享的。这类数据的一个例子可能是用户的偏好语言或书籍类别。这种数据通常被应用程序保留以增强用户体验。它对应用程序本身有用,并且不与其他应用程序共享。
一个共享数据的例子可能是用户在浏览商店时不断添加到收藏中的书籍愿望清单。这类数据可能与其他应用程序共享,也可能不共享。
根据数据的隐私性和类型,可以采用不同的存储机制。应用程序可以决定使用SharedPreferences
、内容提供者、内部或外部存储的文件,甚至是开发者自己的网站来存储这类数据。本章将讨论内容提供者。其他持久性数据存储机制将在第七章《保护应用程序数据》中详细讨论。
服务(Service)
与活动(Activities)不同,服务(Services)没有视觉界面,主要用于后台长时间运行的任务。理想情况下,即使启动它的活动(Activity)不再存在,服务也应该在后台继续运行。任务完成后,服务应该自行停止。适合使用服务执行的任务例如与数据库同步、从网络上传或下载文件、与音乐播放器交互以播放用户选择的曲目,以及应用程序可以绑定获取信息的全局服务。
保护服务的第一步是在清单文件中声明服务。接下来,识别正确服务用例并管理服务生命周期非常重要。这包括启动和停止服务,并创建工作线程以避免阻塞应用程序。在接下来的几节中,我们将逐一介绍这些方面。本章的最后一节是关于绑定器(binders),它是大多数 Android 进程间通信(IPC)的基础,并使服务能够以客户端-服务器的方式使用。
服务声明
应用程序计划启动的所有服务都需要在清单文件中声明。服务声明定义了一旦创建服务后,它将如何运行。清单文件中<service>
标签的语法在以下代码段中展示:
<service android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . . . .
</service>
根据上述声明语法,一个私有的、在全局进程中运行以将书籍存储在数据库中的应用程序服务可以如下声明:
<service
android:name="bookService"
android:process=":my_process"
android:icon="@drawable/icon"
android:label="@string/service_name" >
</service>
默认情况下,服务在应用程序的全局进程中运行。如果应用程序想要在不同的进程中启动服务,可以使用android:process
属性。如果此属性的值以冒号(:
)开头,服务将在应用程序内的新私有进程中启动。如果值以小写字母开头,将创建一个新的全局进程,该进程对 Android 系统的所有应用程序可见和可访问。在上述示例中,服务在其自己的全局进程中运行。应用程序应该有权限创建此类进程。
这个android:enabled
属性定义了系统是否可以实例化服务。默认值为true
。
android:exported
属性限制了服务的暴露。值为true
意味着此服务对应用程序外部可见。如果服务包含意图过滤器(Intent Filter),则该服务对其他应用程序可见。此属性的默认值为true
。
要在隔离的进程中运行服务,且没有任何权限,请将android:isolatedProcess
属性设置为true
。在这种情况下,与服务交互的唯一方式是通过绑定到服务。此属性的默认值为false
。
与活动一样,服务可以通过权限进行保护。这些服务在清单文件中使用android:permission
属性声明。调用组件需要有适当的权限来调用服务,否则从调用中抛出SecurityException
。
服务模式
服务可以在两个上下文中使用。第一种情况是,服务作为一个辅助服务,组件可以启动它来运行长时间运行的任务。这样的服务被称为启动服务。服务的第二种用例是作为向一个或多个应用程序组件提供信息的服务。在这种情况下,服务在后台运行,应用程序组件通过调用bindService()
来绑定服务。这样的服务被称为绑定服务。
启动的服务可以扩展Service
类或IntentService
类。这两种方法的主要区别在于处理多个请求的方式。当扩展Service
类时,应用程序需要处理多个请求,这通过onStartCommand()
方法完成。
IntentService()
类通过排队所有请求并一次处理一个,从而简化了操作,因此开发者无需处理线程问题。如果适用于某种用例,最好使用IntentService
类以避免多线程错误。IntentService
类为任务启动一个工作线程,并且请求会自动排队。任务在onHandleIntent
中完成,就是这样!以下是IntentService
类的一个示例:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
@Override
protected void onHandleIntent(Intent intent) {
// TODO Auto-generated method stub
}
}
绑定服务是客户端服务器的情况,服务作为服务器,客户端绑定到它以获取信息。这是通过使用bindService()
方法完成的。当客户端满意时,它们使用unbindService()
从服务中解绑自己。
绑定服务可以服务于一个应用程序的组件,也可以服务于不同应用程序的组件。仅服务于一个应用程序组件的绑定服务可以扩展Binder
类并实现返回IBinder
对象的onBind()
方法。如果一个服务服务于多个应用程序,可以使用信使或Android 接口定义语言(AIDL)工具来生成服务发布的接口。使用信使更容易实现,因为它处理多线程问题。
当绑定到一个服务时,检查活动绑定到的服务身份是非常重要的。这可以通过显式指定服务名称来完成。如果服务名称不可用,客户端可以使用ServiceConnection.onServiceConnected()
来检查它所连接的服务的身份。另一种方法是使用权限检查。
提示
对于启动的服务,onBind()
方法返回 null。
生命周期管理
任何组件都可以通过使用startService()
方法并传递一个 Intent 对象来启动一个服务,如下所示:
Intent intent = new Intent(this, MyService.class);
startService(intent);
与其他任何组件一样,启动的 Service 也可以被 Android 系统销毁,以便为用户交互的进程收集资源。在这种情况下,Service 将根据 onStartCommand
方法中设置的返回值重新启动。以下是一个示例:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
handleCommand(intent);
// Let the service run until it is explicitly stopped
return START_STICKY;
}
重新启动 Service 有以下三种选项:
-
START_NOT_STICKY
:这个选项表示除非有挂起的 Intent,否则 Android 系统不应重新启动 Service。挂起的 Intent 在本章后面会讨论。这个选项最适合可以安全地稍后重新启动并完成未完成工作的场景。 -
START_STICKY
:这个选项表示系统应该启动 Service。如果最初的 Intent 丢失,onStartCommand()
方法会以 null Intent 的形式被启动。这对于即使初始 Intent 丢失,Service 也能恢复其任务的情况最为合适。例如音乐播放器,一旦被系统杀死,可以重新开始播放。 -
START_REDELIVER_INTENT
:在这种情况下,Service 会被重新启动,并且挂起的 Intent 会重新传递给 Service 的onStartCommand()
。例如,通过网络下载文件。
需要特别注意的是,Service 与创建线程是不同的。当启动线程的组件被销毁时,线程会立即被杀死。而默认情况下,Service 在全局应用线程中运行,即使调用它的组件被销毁,Service 仍然保持存活。如果 Service 正在进行一些耗时操作,比如下载大文件,最好在一个单独的线程中进行,以避免阻塞应用程序。
启动的 Service 默认在应用线程中运行。任何可能阻塞的活动都应该在一个单独的线程中进行,以避免在运行应用程序时出现潜在的瓶颈。IntentService
类通过生成一个工作线程来处理这种情况。
两种启动服务都应该在任务完成后通过调用 stopSelf()
来停止自己。任何组件也可以通过使用 stopService()
方法来停止 Service。
当没有更多的客户端绑定到 Service 时,系统会销毁绑定的服务。
注意
Service 可以被启动也可以被绑定。在这种情况下,不要忘记调用 stopSelf()
或 stopService()
来停止 Service 在后台继续运行。
绑定器
Binder是大多数 Android IPC 的核心。它是一个内核驱动程序,所有对 Binder 的调用都通过内核进行。信使也是基于 Binder 的。Binder 的实现可能会令人困惑,只有当服务需要为在不同进程中运行的多应用程序提供服务,并希望自行处理多线程时,才应使用 Binder。Binder 框架集成在操作系统中,因此,如果一个进程打算使用另一个进程的服务,它需要将对象封送进基本类型。操作系统然后将它跨进程边界传递。为了使开发者的这项任务更加容易,Android 提供了 AIDL。下图说明了 Binder 是如何成为所有 Android IPC 的核心的。Binder 通过 AIDL 暴露。意图也被实现为 Binder。但这些复杂性对用户是隐藏的。随着我们向更大的同心圆移动,实现变得更加抽象。
要使用 AIDL 创建绑定服务,我们首先创建 AIDL 文件。然后,使用 Android SDK 工具,我们生成接口。这个接口包含扩展了android.os.Binder
类并实现了onTransact()
方法的stub
方法。客户端接收 Binder 接口的引用,并调用其transact()
方法。数据通过这个通道作为一个Parcel
对象流动。Parcel
对象是可序列化的,因此它可以有效地跨越进程边界。
注意事项
Parcel
对象是为了高性能的 IPC 传输而定义的,因此它们不应用于通用目的的序列化。
如果有多个进程正在使用服务,请注意,一旦公开了 AIDL,就尽量不要更改它,因为其他应用程序可能也在使用它。如果这种更改是绝对必要的,那么至少它应该是向后兼容的。
Binder 在系统中全局唯一,对 Binder 的引用可以用作验证可信组件的共享密钥。保持 Binder 私有始终是一个好主意。任何拥有 Binder 引用的人都可以调用它,并且可以调用transact()
方法。由服务来响应请求。例如,Zygote,这个系统服务,公开了一个任何 Activity 都可以绑定的 Binder。但是调用它的transact()
方法并不意味着它会得到响应。
根据 <service>
标签的 android:process
属性,Binder 可以在同一进程或不同进程中运行。
Binder 通过内核安全地提供调用组件及其权限的身份。可以使用 Binder 的getCallingPid()
和getCallingUid()
方法来检查调用者的身份。Binder 反过来可以调用其他 Binder,在这种情况下,它们可以使用调用 Binder 的身份。要检查调用者的权限,可以使用Context.checkCallingPermission()
。要检查调用者或 Binder 本身是否有特定权限,可以使用Context.checkCallingOrSelfPermission()
。
内容提供者
安卓系统使用内容提供者(Content Providers)来存储数据,如联系人列表、日历和字典。内容提供者是安卓跨进程边界处理结构化数据的机制,也可以在应用内使用。
在大多数情况下,内容提供者的数据存储在 SQL 数据库中。标识符_id
用作主键。与 SQL 一样,用户通过编写查询来访问数据。这些可以是rawQuery()
或query()
,具体取决于它们是原始 SQL 语句还是结构化查询。查询的返回类型是一个指向结果行之一的Cursor
对象。用户可以使用辅助方法如getCount()
、moveToFirst()
、isAfterLast()
和moveToNext()
来导航多行。完成任务后,需要使用close()
关闭Cursor
。
提供者支持许多不同类型的数据,包括整数、长整数、浮点数、双精度数以及作为 64 KB 数组实现的 BLOB(二进制大对象)。提供者还可以返回标准或 MIME 类型。标准 MIME 类型的一个例子是text/html
。对于自定义 MIME 类型,值总是vnd.android.cursor.dir
和vnd.android.cursor.item
,分别用于多行和单行。
下图展示了一个可以抽象数据库、文件甚至远程服务器的内容提供者。应用程序的其他组件可以访问它,其他应用组件也可以访问,前提是它们具有适当的权限。
以下各节将讨论正确声明提供者、定义适当的权限以及避免常见的安全陷阱,这些都是安全访问提供者数据所必需的。
提供者声明
应用程序想要使用的任何提供者都必须在清单文件中声明。provider
标签的语法如下:
<provider android:authorities="list"
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:grantUriPermissions=["true" | "false"]
android:icon="drawable resource"
android:initOrder="integer"
android:label="string resource"
android:multiprocess=["true" | "false"]
android:name="string"
android:permission="string"
android:process="string"
android:readPermission="string"
android:syncable=["true" | "false"]
android:writePermission="string" >
. . . . . . .
</provider>
根据前面的声明语法,可以如下声明一个自定义提供者,用于维护用户愿望清单中的书籍列表。该提供者具有读写权限,客户端可以请求对/figures
路径的临时访问。
<provider
android:authorities="com.example.android.books.contentprovider"
android:name=".contentprovider.MyBooksdoContentProvider"
android:readPermission="com.example.android.books.DB_READ"
android:writePermission="com.example.android.book.DB_WRITE">
<grant-uri-permission android:path="/figures/" />
<meta-data android:name="books" android:value="@string/books" />
</provider>
字符串android:authorities
列出了应用程序公开的提供者。例如,如果一个提供者的 URI 是content://com.example.android.books.contentprovider/wishlist/English
,content://
是方案,com.example.android.books.contentprovider
是权限,而wishlist/English
是路径。至少需要指定一个权限。如果有多权限,应该使用分号分隔。它应该遵循 Java 命名空间规则以避免冲突。
布尔型标签android:enabled
指定系统可以启动提供者。如果值为 true,系统可以启动。如果值为 false,系统则不能启动提供者。需要注意的是,为了实现这一点,<application>
标签和<provider>
标签中的android:enabled
属性都需要为 true。
如果提供商被发布给其他应用程序,android:exported
会被设置为 true。对于将android:targetSdkVersion
或android:minSdkVersion
设置为 16 或更低的应用程序,默认值为 true。对于所有其他应用程序,默认值是 false。
属性标签android:grantUriPermissions
用于提供一次性访问受权限保护的数据,否则该数据无法被组件访问。如果此功能设置为true
,则允许组件绕过android:readPermission
、android:writePermission
和android:permission
属性施加的限制,并允许访问内容提供商的任何数据。如果此属性设置为false
,则权限只能授予<grant-uri-permission>
标签中列出的数据集。此标签的默认值为 false。
整数android:initOrder
是提供商初始化的顺序。数字越大,初始化越早。如果应用程序中的提供商之间存在依赖关系,这个属性特别重要。
字符串android:label
是内容提供商的用户可读标签。
布尔值android:multiprocess
属性,如果设置为 true,允许系统在每个与应用程序交互的应用程序进程中创建提供商的实例。这样可以避免进程间通信的开销。默认值为 false,意味着提供商仅在定义它的应用程序进程中实例化。
字符串android:permission
标签声明了客户端应具备的与提供商交互的权限。
字符串android:readPermission
和字符串android:writePermission
分别定义了客户端应具备的读取和写入提供商数据的权限。如果定义了这些权限,它们将覆盖android:permission
的值。值得注意的是,尽管字符串android:writePermission
只允许对数据库进行写操作,但它通常会使用WHERE
子句,一个聪明的工程师可以绕过这些来读取数据库。因此,写权限也应被视为读权限。
android:process
属性定义了提供商应该运行在哪个进程中。通常,提供商运行在与应用程序相同的进程中。但是,如果需要将进程运行在单独的私有进程中,可以分配一个以冒号(:
)开头的名称。如果名称以小写字母开头,则提供商将在全局进程中实例化,以实现跨应用程序共享。
android:syncable
属性通过设置为true
允许数据同步到服务器。值为false
时不允许数据同步到服务器。
<provider>
标签可以包含三个子标签。
第一个是具有以下语法的<grant-uri-permission>
:
<grant-uri-permission android:path="string"
android:pathPattern="string"
android:pathPrefix="string" />
另一个是具有以下语法的<path-permission>
标签:
<path-permission android:path="string"
android:pathPrefix="string"
android:pathPattern="string"
android:permission="string"
android:readPermission="string"
android:writePermission="string" />
第三个是<meta-data>
标签,它定义了与提供商相关的元数据,如下所示:
<meta-data android:name="string"
android:resource="resource specification"
android:value="string" />
注意
要提供提供者级别的单一读写权限,分别使用android:readPermission
和android:writePermission
。要提供提供者级别的全面读写权限,请使用android:permission
属性。要启用临时权限,请设置android:grantUriPermissions
属性。你也可以使用<grant-uri-permission>
子元素来实现同样的功能。要启用路径级别的权限,请使用<provider>
的<path-permission>
子元素。
其他安全考虑
内容提供者扩展了ContentProvider
抽象类。这个类有六个方法,如query()
、insert()
、update()
、delete()
、getType()
和onCreate()
,都需要被实现。如果提供者不支持某些功能,应该返回一个异常。这个异常应该能够跨进程边界进行通信。
如果多个线程正在读取和写入提供者数据,同步可能是一个问题。这可以通过使用关键字synchronize
使所有先前提到的方法同步来解决,这样只有一个线程可以访问提供者。另外,可以设置android:multipleprocess=true
,以便为每个客户端创建一个实例。在这种情况下,需要平衡延迟和性能问题。
在某些情况下,为了维护数据完整性,可能需要以特定格式在提供者中输入数据。例如,可能需要每个元素都附加一个标签。为了实现这一点,客户端可能会决定不直接调用ContentProvider
和ContentResolver
类。相反,可以委托一个活动与提供者进行接口交互。所有需要访问提供者数据的客户端都应该向这个活动发送一个 Intent,然后这个活动执行预期的操作。
如果向查询中输入的值没有得到验证,内容提供者很容易受到 SQL 注入的攻击。以下是发生这种情况的一个示例:
// mUserInput is the user input
String mSelectionClause = "var = " + mUserInput;
恶意用户可以在这里输入任何文本。它可能是nothing; DROP TABLE *;
,这将删除表。开发人员应该对任何 SQL 查询应用同样的判断。用户数据应该是参数化的,并经过检查以排除可能的恶意行为。
用户可能会决定使用正则表达式来检查用户输入的输入语法。以下代码段展示了如何验证用户输入的字母数字字符。该代码段使用了String
类的matches
函数。
if (myInput.length() <= 0) {
valid = false;
} else if (!myInput.matches("[a-zA-Z0-9 ]+")) {
valid = false;
} else {
valid = true;
}
在数据库中存储数据时,你可能会想在存储之前对敏感信息(如密码和信用卡信息)进行加密。请注意,加密某些字段可能会影响你索引和排序字段的能力。此外,还有一些开源工具,如针对 Android 的 SQLCipher(sqlcipher.net
),它使用 256 位 AES 提供了完整的 SQLite 数据库加密。
广播接收器
在 API 级别 1 中引入,广播接收器是一种应用程序从系统或其他应用程序接收 Intent 的机制。接收器的美妙之处在于,即使应用程序没有运行,它仍然可以接收到可以触发进一步事件的 Intent。用户不会察觉到广播。例如,一个应用程序打算在系统启动后立即启动后台服务,可以注册 Intent.ACTION_BOOT_COMPLETE
系统 Intent。想要根据新的时区自定义自己的应用程序可以注册 ACTION_TIMEZONE_CHANGED
事件。下面显示了发送广播 Intent 的服务的一个示例。已使用 Android 系统注册此类广播的接收器将收到广播 Intent。
应用程序可以在清单文件中声明接收器。然后接收器类扩展 BroadcastReceiver
类并实现 onReceive()
方法。或者应用程序也可以动态创建并注册接收器,使用 Context.registerReceiver
。
接收器声明
接收器可以在清单文件中如下声明:
<receiver android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</receiver>
作为一个例子,假设有两个应用程序。第一个应用程序允许用户搜索书籍并将书籍添加到愿望清单中。第二个应用程序监听一个书籍被添加到愿望清单的 Intent。第二个应用程序然后将愿望清单与服务器上的列表同步。第二个应用程序清单文件中的示例接收器声明可能如下所示:
<receiver
android:name="com.example.android.book2.MessageListener" >
<intent-filter>
<action
android:name="com.example.android.book1.my-broadcast" />
</intent-filter>
</receiver>
接收器 com.example.android.book2.MessageListener
是一个公共接收器,它监听来自应用程序 com.example.android.book1
的事件。intent-filter
标签过滤出 Intent。
应用程序 book1
可以如下发送 Intent:
Intent intent = new Intent();
intent.setAction("com.example.android.book1.my-broadcast");
sendBroadcast(intent);
<receiver>
标签的属性如下所述:
-
android:enabled
:将此属性设置为 true 允许系统实例化接收器。此属性的默认值是 true。此标签必须与<application>
的android:enabled
属性结合使用。两者都必须为 true,系统才能实例化它。 -
android:exported
:将此属性设置为 true 使你的接收器对所有系统中的应用程序可见。如果设置为 false,则它只能接收来自同一应用程序或具有相同用户 ID 的应用程序的 Intent。如果你的应用程序没有 Intent 过滤器,那么默认值是 false,因为它假定这个接收器对你来说是私有的。如果你定义了 Intent 过滤器,那么默认值是 true。在我们前面的示例中,我们确实有 Intent 过滤器,所以接收器对系统的其余部分是可见的。 -
android:name
:这是实现接收器的类的名称。这是一个必需的属性,应该是类的完全限定名。声明接收器后,应尽量不要更改名称,因为其他应用程序可能会使用它,更改名称将破坏它们的功能。 -
android:permission
:你可以通过权限保护你的接收器。使用此属性,你可以指定发送意图到你的接收器的组件应该具有的权限。如果这里没有列出权限,则使用<application>
标签的权限。如果那里也没有指定权限,那么你的接收器将完全不受保护。 -
android:process
:默认情况下,接收器在应用程序进程中实例化。如果你愿意,可以在这里声明一个进程的名称。如果名称以冒号(:
)开头,它将在应用程序内的私有进程中实例化。如果以小写字母开头,并且你的应用程序有权限这样做,它将在全局进程中运行。
安全地发送和接收广播。
广播分为两种类型:普通广播和有序广播。普通广播通过Context.sendBroadcast()
以异步方式发送,所有监听它的接收器都将收到它。有序广播通过Context.sendOrderedBoradcast
发送,一次只传递给一个接收器。接收器添加其结果并将其发送给下一个接收器。可以使用 Intent Filter 中的android:priority
属性设置顺序。如果有多个具有相同优先级的过滤器,则接收广播的顺序是随机的。
广播是异步的。你发送它们,但不能保证接收器一定能收到。在这种情况下,应用程序必须优雅地处理。
广播可以包含额外的信息。任何监听广播的接收器都可以接收到发送的广播。因此,明智的做法是在广播中不要发送任何敏感信息。此外,可以通过权限保护广播。这是通过在sendBroadcast()
方法中提供权限字符串来完成的。只有通过使用<uses-permission>
声明适当权限的应用程序才能接收它。同样,可以在sendOrderedBroadcast()
方法中添加权限字符串。
当一个进程仍在执行onReceive()
时,它被视为前台进程。一旦进程离开了onReceive()
方法,它就被视为非活动进程,系统将尝试杀死它。在onReceive()
方法中执行的任何异步操作可能会被杀死。例如,当接收到广播时启动服务应该使用Context.startService()
。
粘性广播会在手机关机或某些组件移除之前一直存在。当广播中的信息更新时,广播会使用新信息进行更新。任何拥有BROADCAST_STICKY
权限的应用程序都可以移除或发送粘性广播,因此不要在其中放置任何敏感信息。此外,粘性广播不能通过权限保护,因此应谨慎使用。
可以在接收器上实施权限。如前所述,这可以通过在清单文件中添加权限或在registerReceiver()
方法中动态添加来实现。
通过设置Intent.setPackage
,从冰淇淋三明治开始,你可以限制广播只被一个应用程序接收。
Intent
类中定义了一些系统广播动作。这些事件由系统触发,应用程序无法触发它们。接收者可以注册监听这些事件中的任何一个。这些动作包括ACTION_TIMEZONE_CHANGED
、ACTION_BOOT_COMPLETED
、ACTION_PACKAGE_ADDED
、ACTION_PACKAGE_REMOVED
、ACTION_POWER_DISCONNECTED
和ACTION_SHUTDOWN
。
本地广播
如果广播仅针对应用程序内的组件,最好使用LocalBroadcastManager
帮助类。这个帮助类是 Android 支持包的一部分。除了比发送全局广播更有效之外,它还更安全,因为它不会离开应用程序进程,其他应用程序也无法看到它。本地广播不需要在清单中声明,因为它仅限于应用程序内部。
可以如下创建本地广播:
Intent intent = new Intent("my-local-broadcast");
Intent.putExtra("message", "Hello World!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
下面的代码段监听一个本地广播:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... other code goes here
LocalBroadcastManager.getInstance(this).registerReceiver(
mMessageReceiver, new IntentFilter("my-local-broadcast"));
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String message = intent.getStringExtra("message");
Log.d("Received local broadcast" + message);
// ... other code goes here
}
};
意图
意图是 Android 组件间通信的机制。意图是异步的,组件发出它们,而接收组件有责任验证传入意图的数据并据此采取行动。Android 系统使用意图来启动活动或服务,与服务通信,广播事件或变化,使用待定意图接收通知,以及查询内容提供者。
对于每个组件,有不同的机制来处理意图。因此,发送到活动、服务和广播接收器的意图只由 Android 系统发送给它们各自的对应组件。例如,使用Context.startActivity()
发送出去以启动活动的的事件将只解决与意图标准匹配的活动。同样,使用Context.sendBroadcast()
发送的广播将只被接收者接收,而不是其他组件。
在意图被发送出去之前,重要的是要检查是否有组件来处理意图。如果没有组件来处理意图,应用程序将会崩溃。可以使用PackageManager
类的queryIntentActivities()
方法查询匹配的意图。
注意
任何恶意应用程序都可以向暴露的组件发送 Intent。在对其采取行动之前,验证输入是您组件的责任。
Intent 基本上是在组件间传递的序列化对象。这个对象包含了一些被其他组件用来执行操作的信息。例如,使用用户登录凭据登录用户的 Activity 可能会启动另一个 Activity,使用Context.startActivity()
加载用户之前选择的书籍。在这种情况下,Intent 可能包含用户的账户名,该账户名将用于从服务器获取存储的书籍。
Intent
对象包含以下四种信息:
-
组件名称(Component Name):只有在显式 Intent 的情况下才需要组件名称。如果与外部组件通信,它必须是完全限定类名;如果是内部组件,则只需类名。
-
动作字符串(Action String):动作字符串是应该执行的动作。例如,动作字符串
ACTION_CALL
发起电话呼叫。广播动作ACTION_BATTERY_LOW
是关于低电量对应用程序的警告。 -
数据(Data):这是带有 MIME 类型的数据的 URI。例如,对于
ACTION_CALL
,数据类型将为tel:
。数据和数据的类型是相辅相成的。为了处理某些数据,了解其类型很重要,以便可以适当地处理。 -
类别(Category):类别提供了关于组件可以接收的 Intent 类型附加信息,从而增加了进一步的限制。例如,浏览器可以安全地调用具有
CATEGORY_BROWSERABLE
类别的 Activity。
Intents 是异步的,因此不期望有结果。在 Activity 的情况下,Intent 也可以用于启动一个 Activity 以获取结果。这是通过使用Context.startActivityForResult()
完成的,结果通过finish()
方法返回给调用 Activity。
用于广播的 Intent 通常是关于刚刚发生动作的通告。广播接收者注册监听这些事件。一些示例包括ACTION_PACKAGE_ADDED
、ACTION_TIME_TICK
、ACTION_BOOT_COMPLETED
。在这种情况下,Intent 就像一个触发器,一旦事件发生就会执行某些动作。
注意
不要在Intent
对象中放置任何敏感信息。应使用其他机制,如可以通过权限保护的内容提供者(Content Provider)来在组件间共享信息。
接收组件通过使用getIntent().getExtras()
获取附加在Intent
类上的额外信息。安全的编程实践要求对此输入进行验证,并确保其值为可接受值。
显式 Intents
组件可以发送一个特定的 Intent,只针对一个组件。为此,组件应知道目标组件的完全限定名称。应用 A中的 Activity 向应用 B中的 Activity 发送显式 Intent,可以图形化表示如下:
例如,活动可以使用以下代码显式地与名为ViewBooksActivity
的内部活动通信:
Intent myIntent = new Intent (this, ViewBooksActivity.class);
startActivity(myIntent);
如果ViewBooksActivity
是一个外部活动,则组件名称应该是类的完全限定名。可以这样操作:
Intent myIntent = new Intent (this, "com.example.android.Books.ViewBooksActivity.class");
startActivity(myIntent);
由于意图可以被任何应用拦截,如果组件名称可用,最好显式调用该组件。
隐式意图
如果不知道组件的完全限定名,可以通过指定接收组件需要用它执行的动作来隐式调用该组件。然后系统通过匹配Intent
对象中指定的标准,识别出最适合处理意图的组件。以下是一个隐式意图的说明:应用 A中的活动发出一个意图,系统搜索可以处理此类意图的相关组件(基于它们的意图过滤器和权限)。
以下是一些隐式意图的示例:
// Intent to view a webpage
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.google.com"));
// Intent to dial a telephone number
Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:4081112222"));
//Intent to send an email
Intent intent = new Intent(Intent.ACTION_SEND);
emailIntent.setType(HTTP.PLAIN_TEXT_TYPE);
emailIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"me@example.com"});
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Hello Intent!");
emailIntent.putExtra(Intent.EXTRA_TEXT, "My implicit intent");
意图过滤器
为了让系统解析组件,需要在清单文件中用适当的标识符声明该组件。这项任务是通过意图过滤器完成的。意图过滤器是为活动使用<activity>
、<service>
或<receiver>
声明中的<intent-filter>
子标签定义的。在解析意图的适当活动时,系统只考虑Intent
对象的三个方面:动作、数据(URI 和 MIME 类型)和类别。所有这些意图方面必须匹配才能成功解析。组件名称仅用于显式意图。
意图过滤器必须包含<action>
子标签,并且可能包含<category>
和<data>
。以下是一些<intent-filter>
声明的示例。
以下标签标识了应用的启动点活动:
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
一个允许用户请求book
类型数据的活动可以定义如下:
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/vnd.example.book"/>
</intent-filter>
意图过滤器不是安全边界,不应依赖它们来保证安全。意图过滤器不能通过权限来保护。此外,具有意图过滤器的任何组件都将成为导出组件,任何应用都可以向这个组件发送意图。
待定意图
在意图的情况下,接收应用以其自身的权限执行代码,就如同它是接收应用的一部分。在待定意图的情况下,接收应用使用原始应用的标识和权限,并代表其执行代码。
因此,待定意图是应用程序提供给另一个应用程序的令牌,以便另一个应用程序可以以原始应用程序的权限和身份执行一段代码。即使发送应用程序的进程被杀死或销毁,待定意图仍将执行。待定意图的此属性可以很好地用于在事件发生后向原始应用程序发送通知。待定意图可以是显式的也可以是隐式的。
为了增加安全性,使得只有一个组件接收意图,可以使用setComponent()
方法将组件内嵌到意图中。默认情况下,待定意图(pending Intent)不能被接收组件修改。这对于安全来说是有好处的。接收组件唯一可以编辑的部分是extras
。然而,发送者可以设置标志,明确允许接收组件编辑PendingIntent
。为此,发送者需要使用fillIn(Intent, int)
方法的规则。例如,如果发送者希望接收者覆盖数据字段,即使它已经被设置,发送者可以设置FILL_IN_DATA=true
。这是一个非常敏感的操作,应当谨慎处理。
总结
在本章中,我们回顾了 Android 系统的四个组件——活动(Activities)、服务(Services)、内容提供者(Content Providers)和广播接收器(Broadcast Receivers),以及组件间通信机制——意图(Intents)和绑定器(Binders)。安全性的起点是这些组件的安全声明。按照安全的一般规则,暴露最少的信息总是一个好主意。所有 Android 组件都通过权限进行保护。意图(Intents)是异步组件,应始终验证其输入。意图过滤器(Intent Filters)是减少应用程序攻击面的好方法,但显式意图(Explicit Intent)仍然可以向其发送意图。现在我们了解了 Android 组件和通信机制,让我们在下一章中详细查看 Android 权限。
第三章:权限
本章的重点是权限。权限是 Android 应用程序的必要组成部分,几乎所有的应用程序开发者和用户都会在不同时间遇到它们。正如我们在第一章中讨论的,Android 安全模型 - 大局观,安装时的应用程序审查是最重要的安全关卡。这一步是用户的全或无决策;用户要么接受所有列出的权限,要么拒绝下载应用。因此,作为 Android 手机的用户,了解权限对于明智地决定安装哪个应用程序至关重要。权限是保护组件和用户数据的基础。
本章从介绍 Android 系统中现有的权限开始。我们讨论了四种权限保护级别:普通、危险、签名和签名或系统。然后,我们将讨论如何使用权限来保护应用程序及其组件。接下来,我们学习如何通过添加用户定义的权限来扩展权限模型。这一节将讨论权限组、权限树以及在清单文件中创建新权限的语法。
权限保护级别
在应用程序层面,Android 安全基于权限。使用这种基于权限的模型,Android 系统保护系统资源,如相机和蓝牙,以及应用程序资源,如文件和组件。应用程序应该有权限去操作或使用这些资源。任何打算使用这些资源的应用程序需要向用户声明它将访问这些资源。例如,如果一个应用程序将发送和读取短信,它需要在清单文件中声明 android.permission.SEND_SMS
和 android.permission.READ_SMS
。
权限也是应用程序之间进行访问控制的有效方法。应用程序的清单文件包含一个权限列表。任何希望访问此应用程序资源的外部应用程序应具备这些权限。下一章将详细讨论这一点。
所有 Android 权限都在 Manifest.permission
类中声明为常量。然而,这个类并没有提到权限的类型。这可以用来检查 Android 源代码。我尝试在以下部分列出一些这些权限。权限列表会根据功能的变化而变化,因此最好参考 Android 源代码以获取最新的权限列表。例如,android.permission.BLUETOOTH
从 API 级别 1 开始就有,而 android.permission.AUTHENTICATE_ACCOUNTS
是在 API 5 中添加的。您可以在 source.android.com 获取获取 Android 源代码的信息。
所有 Android 权限都属于四个保护级别之一。任何保护级别的权限都需要在清单文件中声明。第三方应用只能使用保护级别为 0 和 1 的权限。以下将讨论这些保护级别:
-
普通权限:这一级别(级别 0)的权限对用户造成不了多大伤害。它们通常不会让用户花钱,但可能会引起用户的一些烦恼。下载应用时,可以通过点击查看全部箭头来查看这些权限。这些权限会自动授予应用。例如,权限
android.permission.GET_PACKAGE_SIZE
和android.permission.FLASHLIGHT
分别允许应用获取任何包的大小和访问闪光灯。以下是本书编写时 Android 系统中存在的一些普通权限列表。
用于设置用户偏好的权限包括:
-
android.permission.EXPAND_STATUS_BAR
展开状态栏 -
android.permission.KILL_BACKGROUND_PROCESSES
结束后台进程 -
android.permission.SET_WALLPAPER
设置壁纸 -
android.permission.SET_WALLPAPER_HINTS
设置壁纸提示 -
android.permission.VIBRATE
震动 -
android.permission.DISABLE_KEYGUARD
禁用键盘锁 -
android.permission.FLASHLIGHT
闪光灯
允许用户访问系统或应用信息的权限包括:
-
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
访问位置额外命令 -
android.permission.ACCESS_NETWORK_STATE
访问网络状态 -
android.permission.ACCESS_WIFI_STATE
访问 WiFi 状态 -
android.permission.BATTERY_STATS
电池统计 -
android.permission.GET_ACCOUNTS
获取账户 -
android.permission.GET_PACKAGE_SIZE
获取包大小 -
android.permission.READ_SYNC_SETTINGS
读取同步设置 -
android.permission.READ_SYNC_STATS
读取同步统计 -
android.permission.RECEIVE_BOOT_COMPLETED
接收开机完成 -
android.permission.SUBSCRIBED_FEEDS_READ
读取订阅源 -
android.permission.WRITE_USER_DICTIONARY
写入用户词典
用户应该谨慎请求的权限包括
android.permission.BROADCAST_STICKY
,它允许应用发送粘性广播,即使广播已经送达后仍然保持存活。 -
-
危险权限:这一保护级别(级别 1)的权限总是向用户显示。向应用授予危险权限允许它们访问设备功能和数据。这些权限可能导致用户隐私或财务损失。例如,授予如
android.permission.ACCESS_FINE_LOCATION
和android.permission.ACCESS_COARSE_LOCATION
之类的危险权限,允许应用访问用户的位置,如果应用不需要这种功能,可能会成为隐私问题。同样,授予应用android.permission.READ_SMS
和android.permission.SEND_SMS
权限,允许应用发送和接收短信,这也可能涉及隐私问题。用户可以在任何时候进入设置,选择应用来查看已授予应用的权限。参考以下两张图片,它们展示了 Gmail 应用的权限。第一张图片显示了总是向用户显示的危险权限。注意显示全部的下拉菜单按钮。此选项显示了应用请求的所有权限。注意硬件控制权限,以及默认不向用户显示的正常权限。
以下是本书撰写时 Android 系统中一些危险权限的列表。
一些危险权限对用户来说可能是代价高昂的。例如,发送短信或订阅付费内容的应用可能导致用户花费大量资金。以下是一些其他例子:
-
android.permission.RECEIVE_MMS
-
android.permission.RECEIVE_SMS
-
android.permission.SEND_SMS
-
android.permission.SUBSCRIBED_FEEDS_WRITE
有权改变手机状态的权限包括以下内容。这些权限应谨慎使用,因为它们可能导致系统不稳定,引起用户烦恼,并使系统安全性降低。例如,挂载和卸载文件系统的权限可以改变手机的状态。任何具有录音权限的恶意应用都可以轻松地用垃圾音频占用手机内存。以下是一些例子:
-
android.permission.MODIFY_AUDIO_SETTINGS
-
android.permission.MODIFY_PHONE_STATE
-
android.permission.MOUNT_FORMAT_FILESYSTEMS
-
android.permission.WAKE_LOCK
-
android.permission.WRITE_APN_SETTINGS
-
android.permission.WRITE_CALENDAR
-
android.permission.WRITE_CONTACTS
-
android.permission.WRITE_EXTERNAL_STORAGE
-
android.permission.WRITE_OWNER_DATA
-
android.permission.WRITE_SETTINGS
-
android.permission.WRITE_SMS
-
android.permission.SET_ALWAYS_FINISH
-
android.permission.SET_ANIMATION_SCALE
-
android.permission.SET_DEBUG_APP
-
android.permission.SET_PROCESS_LIMIT
-
android.permission.SET_TIME_ZONE
-
android.permission.SIGNAL_PERSISTENT_PROCESSES
-
android.permission.SYSTEM_ALERT_WINDOW
一些危险权限可能存在隐私风险。允许用户读取短信、日志和日历的权限可能被僵尸网络和木马轻易利用,按照远程主人的命令执行有趣的操作。以下是一些其他例子:
-
android.permission.MANAGE_ACCOUNTS
-
android.permission.MODIFY_AUDIO_SETTINGS
-
android.permission.MODIFY_PHONE_STATE
-
android.permission.MOUNT_FORMAT_FILESYSTEMS
-
android.permission.MOUNT_UNMOUNT_FILESYSTEMS
-
android.permission.PERSISTENT_ACTIVITY
-
android.permission.PROCESS_OUTGOING_CALLS
-
android.permission.READ_CALENDAR
-
android.permission.READ_CONTACTS
-
android.permission.READ_LOGS
-
android.permission.READ_OWNER_DATA
-
android.permission.READ_PHONE_STATE
-
android.permission.READ_SMS
-
android.permission.READ_USER_DICTIONARY
-
android.permission.USE_CREDENTIALS
-
-
签名权限:这一保护级别(第 2 级)的权限允许同一开发者编写的两个应用程序互相访问对方的组件。如果下载的应用程序具有与声明权限的应用程序相同的证书,则自动授予此权限。例如,应用程序 A 定义了一个权限
com.example.permission.ACCESS_BOOK_STORE
。由与应用程序 A 相同证书签名的应用程序 B 在其清单文件中声明了它。由于应用程序 A 和 B 具有相同的证书,因此当安装应用程序 B 时,此权限不会显示给用户。用户当然可以使用查看全部来查看它。应用这一组权限的应用程序可以执行非常强大的操作。例如,有了android.permission.INJECT_EVENTS
权限,应用程序可以向任何应用程序注入按键、触摸和轨迹球事件,而android.permission.BROADCAST_SMS
可以广播短信确认。这一保护组中由 Android 系统定义的权限仅保留给系统应用。这一级别的某些权限允许应用程序使用系统级别的功能。例如,
ACCOUNT_MANAGER
权限允许应用程序使用账户验证器,而 BRIK 权限允许应用程序锁定手机。以下是编写本书时一些签名权限的列表。要获取完整的参考资料,请查看 Android 源代码或Manifest.permission
类:-
android.permission.ACCESS_SURFACE_FLINGER
-
android.permission.ACCOUNT_MANAGER
-
android.permission.BRICK
-
android.permission.BIND_INPUT_METHOD
-
android.permission.SHUTDOWN
-
android.permission.SET_ACTIVITY_WATCHER
-
android.permission.SET_ORIENTATION
-
android.permission.HARDWARE_TEST
-
android.permission.UPDATE_DEVICE_STATS
-
android.permission.CLEAR_APP_USER_DATA
-
android.permission.COPY_PROTECTED_DATA
-
android.permission.CHANGE_COMPONENT_ENABLED_STATE
-
android.permission.FORCE_BACK
-
android.permission.INJECT_EVENTS
-
android.permission.INTERNAL_SYSTEM_WINDOW
-
android.permission.MANAGE_APP_TOKENS
这一级别的某些权限允许应用程序发送系统级别的广播和意图,如发送意图和短信。这些权限包括:
-
android.permission.BROADCAST_PACKAGE_REMOVED
-
android.permission.BROADCAST_SMS
-
android.permission.BROADCAST_WAP_PUSH
这一级别的其他权限允许应用程序访问第三方应用程序无法访问的系统级别数据。这些权限包括:
-
android.permission.PACKAGE_USAGE_STATS
-
android.permission.CHANGE_BACKGROUND_DATA_SETTING
-
android.permission.BIND_DEVICE_ADMIN
-
android.permission.READ_FRAME_BUFFER
-
android.permission.DEVICE_POWER
-
android.permission.DIAGNOSTIC
-
android.permission.FACTORY_TEST
-
android.permission.FORCE_STOP_PACKAGES
-
android.permission.GLOBAL_SEARCH_CONTROL
-
-
签名或系统权限:与签名保护级别一样,此权限授予与定义该权限的应用具有相同证书的应用。此外,此保护级别还包括与 Android 系统映像具有相同证书的应用。这个权限级别主要用于手机制造商、运营商和系统应用构建的应用。第三方应用不允许使用这些权限。这些权限允许应用执行一些非常强大的功能。例如,权限
android.permission.REBOOT
允许应用重启设备。权限android.permission.SET_TIME
允许应用设置系统时间。在撰写本书时,一些 SignatureOrSystem 权限列表如下:
-
android.permission.ACCESS_CHECKIN_PROPERTIES
-
android.permission.BACKUP
-
android.permission.BIND_APPWIDGET
-
android.permission.BIND_WALLPAPER
-
android.permission.CALL_PRIVILEGED
-
android.permission.CONTROL_LOCATION_UPDATES
-
android.permission.DELETE_CACHE_FILES
-
android.permission.DELETE_PACKAGES
-
android.permission.GLOBAL_SEARCH
-
android.permission.INSTALL_LOCATION_PROVIDER
-
android.permission.INSTALL_PACKAGES
-
android.permission.MASTER_CLEAR
-
android.permission.REBOOT
-
android.permission.SET_TIME
-
android.permission.STATUS_BAR
-
android.permission.WRITE_GSERVICES
-
android.permission.WRITE_SECURE_SETTINGS
-
应用程序级别权限
有两种方法可以将权限应用于整个应用程序。在第一种情况下,应用程序声明应用程序正常运行所需的权限。因此,将发送短信的应用程序将在清单文件中声明此类权限。在第二种情况下,应用程序可以声明尝试与此应用程序交互的其他应用程序应具有的权限。例如,一个应用程序可以声明任何想要与其组件交互的应用程序都应具有访问摄像头的权限。这两种类型的权限都必须在清单文件中声明。让我们逐一了解它们。
这个 <uses-permission>
标签在 <manifest>
内声明,表示应用程序请求正常运行所需的权限。标签的语法如下:
<uses-permission android:name=" " />
用户在下载应用时,必须接受这些权限。android:name
是权限的名称。此标签的声明示例如下。以下权限声明用户即将安装的应用程序将访问用户的短信:
<uses-permission android:name="android.permission.READ_SMS"/>
<application>
标签有一个名为android:permission
的属性,用于声明组件的通用权限。任何尝试与此应用程序的组件交互的应用程序都需要这些权限。以下代码显示了这一点。以下代码表明,与MyApplication
的任何组件交互的应用程序都应该有访问摄像头的权限:
<application android:name="MyApplication" android:icon="@drawable/icon" android:label="@string/app_name""android.permission.CAMERA"/>
正如下一节所讨论的,各个组件也可以设置权限。组件权限会覆盖使用<application>
标签设置的权限。上述方法是声明所有组件的通用权限的最佳位置。
组件级别权限
所有 Android 组件都可以使用权限进行保护。以下图示说明了这一概念:
让我们讨论一下每个组件的权限声明和实施。
活动
任何活动都可以通过在<activity>
标签的活动声明中调用权限来进行保护。例如,具有自定义权限com.example.project.ORDER_BOOK
的活动OrderActivity
将如下声明。任何尝试启动OrderActivity
的组件都需要拥有这个自定义权限。
<activity android:name=".OrderActivity" android:permission="com.example.project.ORDER_BOOK" android:exported="false"/>
对于活动,权限实施发生在使用Context.startActivity()
和Context.startActivityForResult()
启动活动时。如果启动组件没有适当的权限,将抛出SecurityException
。
服务
任何服务都可以通过在<service>
标签中列出所需的权限来进行保护。例如,基于关键词识别短信的服务FindUsefulSMS
声明了一个权限android.permission.READ_SMS
。这个权限将如下声明。任何尝试启动FindUsefulSMS
的组件都需要拥有这个权限。
<service android:name=".FindUsefulSMS" android:enabled="true"
android:permission="android.permission.READ_SMS">
</service>
对于服务的权限实施是在使用Context.startService()
启动服务、使用Context.stopService()
停止服务以及使用Context.bindService()
绑定服务时进行的。如果请求的组件没有适当的权限,将抛出SecurityException
。
如果服务公开了一个允许其他应用程序绑定的 Binder 接口,可以在绑定到 Binder 时使用Context.checkCallingPermission()
来检查调用者的权限。
内容提供者
内容提供者可以通过<provider>
标签中指定的权限来保护。以下示例中,任何想要与提供者通信的组件都应该拥有android.permission.READ_SMS
权限:
<provider
android:authorities="com.example.android.books.contentprovider"
android:name=".contentprovider.MyBooksdoContentProvider"
android:grantUriPermissions="true"
android:Permission="android.permission.READ_CALENDAR"/>
正如在第二章 应用构建块 中所讨论的,<provider>
标签也具有细粒度的读写权限属性。为了能够从<provider>
标签中读取,应用程序应具有读取权限。这会在ContentResolver.query()
过程中进行检查。为了能够更新、删除和插入提供者,组件应具有读写权限。这些权限会在ContentResolver.insert()
、ContentResolver.update()
和ContentResolver.delete()
过程中进行检查。如果权限不适当,将导致调用时抛出SecurityException
。
<grant-uri-permission>
标签是<provider>
标签的子标签,用于在有限时间内授予对提供者某些特定数据集的访问权限。考虑一个将短信保存到数据库的应用程序示例。某些短信可能附有照片。为了使应用程序正确查看短信,它会启动图像查看器,而图像查看器可能无法访问提供者。URI(通用资源标识符)权限将允许图像查看器读取特定图片的权限。在前面的示例中,提供者设置了android:grantIriPermissions="true"
,图像查看器将具有对整个提供者的读取权限。这构成了安全风险。为了提供有限的访问权限,提供者可以声明它希望对 URI 权限开放的部分。
URI 权限的语法如下:
<grant-uri-permission android:path="string"
android:pathPattern="string"
android:pathPrefix="string" />
注意
URI 权限不是递归的。
我觉得最有趣的是,我们可以使用通配符和模式来定义我们想要强制实施 URI 权限的提供者的哪些部分。以下是一个例子:
<grant-uri-permission android:pathPattern="/picture/" />
记得在任务完成后使用Context.revokeUriPermission()
撤销 URI 权限。
广播接收器
广播可以通过两种方式使用权限进行保护。在第一种情况下,接收者使用权限保护自己,因此它只接收想要听到的广播。在另一种情况下,广播者选择哪些接收者可以接收广播。我们将在下一节讨论这两种情况。
任何接收者都可以通过在<receiver>
标签中的接收者声明中调用权限来保护。例如,接收者MyListener
声明了一个权限android.permission.READ_SMS
,这将按如下方式声明。MyListener
只接收具有android.permission.READ_SMS
权限的广播者的广播。
<receiver android:name=".MyListener"
android:permission="android.permission.READ_SMS">
<intent-filter>
<action android:name=
"android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
提示
请记住,粘性广播无法通过权限进行保护。
接收广播所需的权限在广播意图传递后被检查,即在调用Context.sendBroadcast()
返回之后。因此,如果广播者没有适当的权限,不会抛出异常;只是广播不会被传递。如果通过使用Context.registerReceiver()
动态创建接收者,可以在创建此接收者时设置权限。
第二种情况,广播者限制哪些接收者可以接收意图,是通过使用sendBroadcast()
方法实现的。以下代码片段定义了一个仅发送给具有android.permission.READ_SMS
权限的应用程序接收者的广播示例:
Intent intent = new Intent();
intent.setAction(MY_BROADCAST_ACTION);
sendBroadcast(intent, "android.provider.Telephony.SMS_RECEIVED");
注意
使用组件声明的权限不会授予应用程序。这是尝试与之交互的组件所属的应用程序应具备的权限。
扩展 Android 权限
开发者可以通过添加自己的权限来扩展权限系统。这些权限将在用户下载应用时向用户展示,因此确保它们被适当地本地化和标记是非常重要的。
添加新权限
开发者可以选择仅添加一个新权限或整个权限树。在清单文件中声明新权限。要添加新权限,应用程序可以使用<permission>
标签声明,如下面的代码片段所示:
<permission android:name="string"
android:description="string resource"
android:icon="drawable resource"
android:label="string resource"
android:permissionGroup="string"
android:protectionLevel=["normal" | "dangerous" |
"signature" | "signatureOrSystem"] />
以下代码片段中用于新权限组的属性描述如下:
-
android:name
:这是正在声明的新的权限名称。 -
android:description
:这详细描述了正在声明的新的权限。 -
android:icon
:这是权限图标。 -
android:label
:这是在安装时向用户显示的标签。 -
android:permissionGroup
:这将为权限分配一个预先存在的用户定义的组或新组。如果没有指定名称,权限不属于任何组,这也是可以的。我将在本节后面讨论如何创建权限组。 -
android:protectionLevel
:这指定了新权限的保护级别。这些保护级别在本章前面已经讨论过。
这样的权限的一个示例可能如下:
<permission android:name="com.example.android.book.READ_BOOKSTORE"
android:description="@string/perm_read_bookstore"
android:label="Read access to books database"
android:permissionGroup="BOOKSTORE_PERMS"
android:protectionLevel="dangerous"/>
为了便于本地化和维护,使用字符串资源总比使用原始字符串要好。
一旦声明了新权限,请确保在<uses-permission>
标签中声明它。
创建权限组
可以使用<permission-group>
标签创建权限组。这是一个逻辑上的权限分组,当向用户展示时,它们将一起呈现。权限组是使用以下语法创建的:
<permission-group android:name="string"
android:description="string resource"
android:icon="drawable resource"
android:label="string resource" />
以下代码片段中用于新权限组的属性描述如下:
-
android:name
:这是新权限组的名称。这是在<permission>
标签中提到的名称。 -
android:description
:这详细描述了正在声明的新权限组。 -
android:icon
:这是权限组的图标。(注意:这里重复了,根据注意事项,不应该重复输出,所以这里不翻译) -
android:label
:这是在安装时显示的标签。(注意:这里重复了,根据注意事项,不应该重复输出,所以这里不翻译)
一个带有书店权限的权限组声明示例如下:
<permission-group android:description="@string/perm_group_bookstore"
android:label="@string/perm_group_bookstore_label"
android:name="BOOKSTORE_PERMS" />
创建权限树
如果需要将权限组织为一个命名空间,以便创建权限树,那么应用程序可以声明一个<permission-tree>
标签。这样的树的一个示例如下:
com.example.android.book
com.example.android.book.READ_BOOK
com.example.android.book.bookstore.READ_BOOKSTORE
com.example.android.book.bookstore.WRITE_BOOKSTORE
这个标签并不定义任何新权限,它只是为你创建一个用于分组权限的命名空间。我看到这个概念被那些有多款应用的开发商使用,所有这些应用都会相互通信。<permission-tree>
标签的语法定义如下:
<permission-tree android:name="string"
android:icon="drawable resource"
android:label="string resource" />
以下是对前述代码片段中使用的新权限组属性描述:
-
android:name
:这是新权限组的名称。名称至少应由三个由点分隔的部分组成,例如com.example.android
是可以的,但com.example
则不行。 -
android:icon
:这是权限组的图标。 -
android:label
:这是在安装时向用户显示的标签。
一个声明示例如下:
<permission-tree android:name="com.example.android.book"
android:label="@string/perm_tree_book" />
总结
权限是 Android 应用安全的核心,本章详细介绍了权限。我们了解了四种权限保护级别,如何使用权限保护组件,以及如何定义新权限。对于开发者和 Android 手机用户来说,了解和掌握权限模型至关重要。现在我们拥有了关于组件、组件间通信和权限的知识,让我们迈向下一章,学习如何定义应用程序的策略文件。
第四章:定义应用程序的策略文件
本章将把我们迄今为止所学的所有内容汇集在一起。我们将使用应用程序组件、意图和权限,并将它们全部放在一起来定义我们应用程序的策略文件。这个策略文件被称为AndroidManifest.xml
,无疑是应用程序最重要的文件。正如您将看到的,这个文件是定义应用程序及其组件访问控制策略的地方。这也是定义应用程序和组件级别特定信息的地方,安卓系统将使用这些信息与您的应用程序交互。
本章从讨论AndroidManfiest.xml
开始。我们将讨论到目前为止尚未讨论的两个重要标签:<manifest>
和<application>
。接下来,我们将讨论在清单文件中可以执行的操作,例如声明权限、与其他应用程序共享进程、外部存储以及管理组件可见性。在您发布应用程序之前,本章将以策略文件的核对清单作为结束讨论。您应该根据您的使用情况调整核对清单。一旦有了全面的核对清单,您就可以在每次准备发布新版本时参考它。
AndroidManifest.xml
文件
所有安卓应用程序都需要有一个清单文件。这个文件必须命名为AndroidManifest.xml
,并且必须放在应用程序的根目录中。这个清单文件是应用程序的策略文件。它声明了应用程序组件、它们的可见性、访问规则、库、特性以及应用程序运行的最低安卓版本。
安卓系统使用清单文件进行组件解析。因此,AndroidManfiest.xml
文件是整个应用程序中最重要文件,定义时需要特别小心,以加强应用程序的安全性。
清单文件是不可扩展的,因此应用程序不能添加自己的属性或标签。以下是如何嵌套这些标签的完整标签列表:
<uses-sdk><?xml version="1.0" encoding="utf-8"?>
<manifest>
<uses-permission />
<permission />
<permission-tree />
<permission-group />
<instrumentation />
<uses-sdk />
<uses-configuration />
<uses-feature />
<supports-screens />
<compatible-screens />
<supports-gl-texture />
<application>
<activity>
<intent-filter>
<action />
<category />
<data />
</intent-filter>
<meta-data />
</activity>
<activity-alias>
<intent-filter> </intent-filter>
<meta-data />
</activity-alias>
<service>
<intent-filter> </intent-filter>
<meta-data/>
</service>
<receiver>
<intent-filter> </intent-filter>
<meta-data />
</receiver>
<provider>
<grant-uri-permission />
<meta-data />
<path-permission />
</provider>
<uses-library />
</application>
</manifest>
我们在前面的章节中已经涵盖了大部分标签。
只有两个标签<manifest>
和<application>
是必需的标签。声明组件没有特定的顺序。
<manifest>
标签声明了应用程序特定的属性。它的声明方式如下:
<manifest xmlns:android="http://schemas.android.
com/apk/res/android"
package="string"
android:sharedUserId="string"
android:sharedUserLabel="string resource"
android:versionCode="integer"
android:versionName="string"
android:installLocation=["auto" | "internalOnly" |
"preferExternal"] >
</manifest>
下面的代码片段展示了<manifest>
标签的一个示例。在这个例子中,包名为com.android.example
,内部版本为 10,用户看到的版本为 2.7.0。安装位置由安卓系统根据存储应用程序的空间决定。
<manifest package="com.android.example"
android:versionCode="10"
android:versionName="2.7.0"
android:installLocation="auto"
>
<manifest>
标签的属性如下:
-
package
:这是包的名称。这是应用 Java 风格的命名空间,例如com.android.example
。这是您应用的唯一 ID。如果您更改已发布应用的名称,它将被视为一个新应用,自动更新将不起作用。 -
android:sharedUserId
:如果两个或更多应用共享同一个 Linux ID,则使用此属性。此属性将在后面的章节中详细讨论。 -
android:sharedUserLabel
:这是共享用户 ID 的用户可读名称,仅当设置了android:sharedUserId
时才有意义。它必须是一个字符串资源。 -
android:versionCode
:这是应用内部用来跟踪修订版本的应用版本代码。更新应用至较新版本时会参考此代码。 -
android:versionName
:这是向用户展示的应用版本。它可以设置为原始字符串或引用,并且仅用于向用户展示。 -
android:installLocation
:此属性定义了 APK 将要安装的位置。此属性将在本章后面详细讨论。
应用标签定义如下:
<application android:allowTaskReparenting=["true" | "false"]
android:backupAgent="string"
android:debuggable=["true" | "false"]
android:description="string resource"
android:enabled=["true" | "false"]
android:hasCode=["true" | "false"]
android:hardwareAccelerated=["true" | "false"]
android:icon="drawable resource"
android:killAfterRestore=["true" | "false"]
android:largeHeap=["true" | "false"]
android:label="string resource"
android:logo="drawable resource"
android:manageSpaceActivity="string"
android:name="string"
android:permission="string"
android:persistent=["true" | "false"]
android:process="string"
android:restoreAnyVersion=["true" | "false"]
android:supportsRtl=["true" | "false"]
android:taskAffinity="string"
android:theme="resource or theme"
android:uiOptions=["none" |
"splitActionBarWhenNarrow"] >
</application>
<application>
标签的一个示例在以下代码片段中展示。在此示例中,设置了应用名称、描述、图标和标签。应用不可调试,Android 系统可以实例化组件。
<application android:label="@string/app_name"
android:description="@string/app_desc"
android:icon="@drawable/example_icon"
android:enabled="true"
android:debuggable="false">
</application>
<application>
标签的许多属性作为应用内部声明的组件的默认值。这些标签包括 permission
、process
、icon
和 label
。其他如 debuggable
和 enabled
的属性是为整个应用设置的。以下将讨论 <application>
标签的属性:
-
android:allowTaskReparenting
:此值可以被<activity>
元素覆盖。它允许当 Activity 被带到前台时,Activity 与其有亲缘关系的 Activity 重新分组。 -
android:backupAgent
:此属性包含应用的备份代理的名称。 -
android:debuggable
:当此属性设置为true
时,应用可以被调试。在将应用发布到市场之前,此值应始终设置为false
。 -
android:description
:这是作为对字符串资源的引用的应用的用户可读描述。 -
android:enabled
:如果此属性设置为true
,Android 系统可以实例化应用组件。此属性可以被组件覆盖。 -
android:hasCode
:如果此属性设置为true
,应用在启动组件时会尝试加载一些代码。 -
android:hardwareAccelerated
:当此属性设置为true
时,应用可以支持硬件加速渲染。它是在 API 级别 11 中引入的。 -
android:icon
:这是作为对可绘制资源的引用的应用图标。 -
android:killAfterRestore
:如果此属性设置为true
,则在完整系统恢复期间恢复应用程序设置后,应用程序将被终止。 -
android:largeHeap
:此属性允许安卓系统为该应用程序创建一个大的 Dalvik 堆,并增加应用程序的内存占用,因此应谨慎使用。 -
android:label
:这是应用程序的用户可读标签。 -
android:logo
:这是应用程序的标志。 -
android:manageSpaceActivity
:此值是管理应用程序内存的活动名称。 -
android:name
:此属性包含将在任何其他组件启动之前实例化的子类的完全限定名称。 -
android:permission
:此属性可以被组件覆盖,并设置客户端应具有与应用程序交互的权限。 -
android:persistent
:通常由系统应用程序使用,此属性允许应用程序始终运行。 -
android:process
:这是组件将要运行的进程的名称。这可以被任何组件的android:process
属性覆盖。 -
android:restoreAnyVersion
:此属性允许备份代理在即使当前存储的备份是由比尝试恢复的应用程序更新的新应用程序创建的情况下,尝试恢复。 -
android:supportsRtl
:当此属性设置为true
时,支持从右到左的布局。它是在 API 级别 17 中添加的。 -
android:taskAffinity
:此属性让所有活动都与包名称有关联,除非活动明确设置。 -
android:theme
:这是对应用程序样式资源的引用。 -
android:uiOptions
:如果此属性设置为none
,则没有额外的 UI 选项;如果设置为splitActionBarWhenNarrow
,则在屏幕受限时会在底部设置一个栏。
在以下各节中,我们将讨论如何使用策略文件处理特定要求。
应用程序策略使用场景
本节讨论如何使用清单文件定义应用程序策略。我使用了使用场景,并且我们将讨论如何在策略文件中实现这些使用场景。
声明应用程序权限
安卓平台上的应用程序必须声明它打算使用哪些资源,以便应用程序能够正常运行。这些权限是用户在下载应用程序时显示的权限。正如在第三章 权限 中所讨论的,应用程序也可以定义自定义权限。应用程序权限应该是描述性的,以便用户能够理解它们。此外,与安全相关的普遍规则是,请求所需的最低权限是很重要的。
应用程序权限在清单文件中通过使用 <uses-permission>
标签声明。以下代码段展示了一个使用 GPS 获取位置信息的基于位置的清单文件的示例:
<uses-permissionandroid:name="android.
permission.ACCESS_COARSE_LOCATION" />
<uses-permissionandroid:name="android.
permission.ACCESS_FINE_LOCATION" />
<uses-permissionandroid:name="android.
permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permissionandroid:name="android.
permission.ACCESS_MOCK_LOCATION" />
<uses-permissionandroid:name="android.permission.INTERNET" />
这些权限将在用户安装应用程序时显示给他们,并且可以通过进入设置菜单下的应用程序进行检查。以下屏幕截图显示了这些权限:
声明外部应用程序的权限
清单文件还声明了一个外部应用程序(不与相同的 Linux ID 运行)需要访问应用程序组件的权限。这可以是策略文件中的两个位置之一:在<application>
标签中或者在<activity>
、<provider>
、<receiver>
和<service>
标签中的组件旁边。
如果应用程序的所有组件都需要某些权限,那么在<application>
标签中指定它们很容易。如果一个组件需要某些特定的权限,那么可以在特定的组件标签中定义。请记住,任何标签中只能声明一个权限。如果一个组件受到权限保护,那么组件权限将覆盖在<application>
标签中声明的权限。
以下是一个应用程序示例,它要求外部应用程序具有android.permission.ACCESS_COARSE_LOCATION
权限才能访问其组件和资源:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:permission="android.
permission.ACCESS_COARSE_LOCATION">
如果一个服务要求访问它的任何应用程序组件都需要访问外部存储,那么可以按如下方式定义:
<service android:enabled="true"
android:name=".MyService"
android:permission="android.
permission.WRITE_EXTERNAL_STORAGE">
</service>
如果策略文件同时具有前面的标签,那么当外部组件请求此服务时,它应该具有android.permission.WRITE_EXTERNAL_STORAGE
权限,因为此权限将覆盖应用程序标签中声明的权限。
具有相同 Linux ID 的应用程序运行
应用程序之间共享数据总是很棘手。维护数据保密性和完整性并不容易。必须根据谁可以访问多少数据来建立适当的访问控制机制。在本节中,我们将讨论如何与内部应用程序(由同一开发密钥签名)共享应用程序数据。
安卓是一个分层架构,其应用程序隔离由操作系统本身强制执行。每当在安卓设备上安装一个应用程序时,安卓系统都会给它分配一个由系统定义的唯一用户 ID。请注意,以下屏幕截图中,两个应用程序example1和example2是作为不同的用户 ID 运行的,分别是app_49和app_50:
然而,一个应用程序可以请求系统分配一个它选择的用户 ID。另一个应用程序也可以请求相同的用户 ID。这会创建紧密耦合,不需要将组件对另一个应用程序可见或创建共享的内容提供者。这种紧密耦合是在所有希望在同一进程中运行的应用程序清单标签中完成的。
下面是两个应用com.example.example1
和com.example.example2
的清单文件片段,它们使用相同的用户 ID:
<manifest xmlns:android="http://schemas.android.
com/apk/res/android"
package="com.example.example1"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.sharedID.example">
<manifest xmlns:android="http://schemas.android.
com/apk/res/android"
package="com.example.example2"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.sharedID.example">
下面的截图展示了当这两个应用在设备上运行时的显示效果。注意,应用com.example.example1
和com.example.example2
现在有了app_113的应用 ID。
你会注意到共享 UID 遵循一定的格式,类似于包名。任何其他命名约定都可能导致错误,例如安装错误:INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID
。
提示
所有共享相同 UID 的应用应该具有相同的证书。
外部存储
从 API 级别 8 开始,安卓提供了支持将安卓应用(APK 文件)存储在外部设备上的功能,例如 SD 卡。这有助于释放手机内部内存。一旦 APK 移动到外部存储,应用所占的唯一内存就是存储在内部内存上的应用私有数据。需要注意的是,即使是 SD 卡上的 APK,DEX(Dalvik 可执行文件)、私有数据目录和本地共享库仍然存储在内部存储上。
在清单文件中添加一个可选属性可以启用这个功能。对于这样的应用,应用信息屏幕会有一个移动到 SD 卡或移动到手机的按钮,具体取决于 APK 当前的存储位置。然后用户可以选择相应地移动 APK 文件。
如果外部设备被卸载或 USB 模式设置为Mass Storage
(设备被用作磁盘驱动器),所有在外部设备上运行的活动和服务都会立即被终止。第七章 保护应用数据中对外部存储及其安全性进行了详细分析。在本节中,我们将讨论如何在策略文件中指定外部存储的首选设置。
通过在应用的清单文件的<manifest>
元素中添加可选属性android:installLocation
,可以启用在外部设备上存储 APK 的功能。属性android:installLocation
可以有以下三个值:
-
InternalOnly
:安卓系统只会在内部存储上安装应用。如果内部存储空间不足,将返回存储错误。 -
PreferExternal
:安卓系统将尝试在外部存储上安装应用。如果外部存储空间不足,应用将被安装在内部存储上。用户可以根据需要将应用从外部存储移动到内部存储,反之亦然。 -
auto
:此选项允许 Android 系统决定应用程序的最佳安装位置。默认的系统策略是首先在内部存储上安装应用程序。如果系统内部内存不足,则将应用程序安装在外部存储上。用户可以根据需要将应用程序从外部存储移动到内部存储,反之亦然。例如,如果将
android:installLocation
设置为Auto
,那么在运行 Android 2.2 以下版本的设备上,系统将忽略此功能,APK 只能安装在内部存储上。以下是具有此选项的应用程序清单文件中的代码片段:<manifest package="com.example.android" android:versionCode="10" android:versionName="2.7.0" android:installLocation="auto" xmlns:android="http://schemas.android. com/apk/res/android">
以下是先前指定的应用程序的屏幕截图。你会注意到在这种情况下启用了移动到 SD 卡功能:
在另一个未设置android:installLocation
的应用程序中,移动到 SD 卡功能被禁用,如下面的屏幕截图所示:
设置组件可见性
应用程序的任何组件,即活动、服务、提供者和接收器都可以被外部应用程序发现。本节讨论了这些场景的细节。
任何活动或服务都可以通过设置android:exported=false
变为私有的。这也是活动的默认值。以下是一个私有活动的两个示例:
<activity android:name=".Activity1" android:exported="false" />
<activity android:name=".Activity2" />
然而,如果你给活动添加了一个意图过滤器(Intent Filter),那么该活动就会对意图过滤器中的意图变得可发现。因此,意图过滤器绝不能作为安全边界依赖。以下是意图过滤器声明的示例:
<activity
android:name=".Activity1"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".Activity2">
<intent-filter>
<action android:name="com.example.android.
intent.START_ACTIVITY2" />
</intent-filter>
</activity>
活动和服务也可以通过外部组件所需的访问权限进行保护。这是通过使用组件标签的android:permission
属性来实现的。
内容提供者可以通过使用android:exported=false
设置为私有访问。这也是提供者的默认值。在这种情况下,只有具有相同 ID 的应用程序才能访问提供者。通过设置提供者标签的android:permission
属性,甚至可以进一步限制此访问。
广播接收器可以通过使用android:exported=false
设置为私有。如果接收器不包含任何意图过滤器,这是接收器的默认值。在这种情况下,只有具有相同 ID 的组件才能向接收器发送广播。如果接收器包含意图过滤器,则它变得可发现,且android:exported
的默认值为false
。
调试
在应用程序开发过程中,我们通常将应用程序设置为调试模式。这允许开发者看到详细的日志,并可以进入应用程序检查错误。这通过在<application>
标签中设置android:debuggable
为true
来实现。为了避免安全漏洞,在发布应用程序之前,将此属性设置为false
非常重要。
根据我的经验,敏感信息的示例包括用户名和密码、内存转储、内部服务器错误,甚至一些有趣的个人笔记,包括服务器状态和开发者对一段代码的看法。
android:debuggable
的默认值是false
。
备份
从 API 级别 8 开始,应用程序可以选择一个备份代理将设备备份到云端或服务器。这可以在清单文件的<application>
标签中设置android:allowBackup
为true
,然后设置android:backupAgent
为一个类名来实现。android:allowBackup
的默认值设置为true
,如果应用程序希望选择退出备份,可以将其设置为false
。android:backupAgent
没有默认值,应该指定一个类名。
这种备份的安全性是有争议的,因为用于备份数据的服务不同,且敏感数据,如用户名和密码可能会被泄露。
整合所有内容
以下示例将我们迄今为止的学习内容应用于分析随 Android SDK 示例RandomMusicPlayer
提供的AndroidManifest.xml
。
清单文件指定这是应用程序com.example.android.musicplayer
的第 1 版,它在 SDK 14 上运行,但支持回退至 SDK 7。应用程序使用了两个权限,分别是android.permission.INTERNET
和android.permission.WAKE_LOCK
。应用程序有一个名为MainActivity
的 Activity 作为入口点,一个名为MusicService
的服务,以及一个名为MusicIntentReceiver
的接收器。
MusicService
定义了名为PLAY
、REWIND
、PAUSE
、SKIP
、STOP
和TOGGLE_PLAYBACK
的自定义动作。
接收器使用 Android 系统定义的动作意图android.media.AUDIO_BECOMING_NOISY
和android.media.MEDIA_BUTTON
。
所有组件都没有受到权限保护。以下屏幕截图显示了AndroidManifst.xml
文件的一个示例:
示例清单
在本节中,我尝试整理了一个建议您在准备发布应用程序版本时参考的示例列表。这是一个非常通用的版本,您应该根据自身使用案例和组件进行适配。在创建清单时,要考虑与整个应用程序相关的问题,与特定组件相关的问题,以及可能由组件和应用程序规范设置共同引发的问题。
应用程序级别
在本节中,我列出了一些问题,在定义应用程序特定偏好时,你应该问自己这些问题。它们可能会影响用户如何看待、存储和感知你的应用程序。你可能需要问的一些应用程序级别的问题如下:
-
你是否希望与其他你开发的应用程序共享资源?
-
你是否指定了唯一的用户 ID?
-
你是否故意或无意地为另一个应用程序定义了这个唯一 ID?
-
-
你的应用程序是否需要一些功能,如摄像头、蓝牙和短信?
-
你的应用程序是否需要所有这些权限?
-
是否有比你所定义的权限更严格的权限?请记住最小权限原则。
-
你的应用程序的所有组件都需要这个权限,还是只有少数需要?
-
再次检查所有权限的拼写。即使权限拼写错误,应用程序也可能编译并运行。
-
如果你定义了此权限,这是否是你需要的正确权限?
-
-
应用程序在哪个 API 级别上工作?
-
你的应用程序能够支持的最小 API 级别是什么?
-
你的应用程序是否需要任何外部库?
-
你在发布前是否记得关闭调试属性?
-
如果你使用了备份代理,那么请记得在这里提及。
-
你是否记得设置一个版本号?这将有助于你在应用程序升级时。
-
你是否希望设置自动升级?
-
你是否记得用你的发布密钥签名应用程序?
-
有时设置特定的屏幕方向将不允许你的应用程序在某些设备上显示。例如,如果你的应用程序只支持竖屏模式,那么它可能不会在只支持横屏模式的设备上显示。
-
你想在哪个位置安装 APK?
-
如果意图没有及时接收,是否有可能会停止工作的服务?
-
你是否希望有一些其他的应用程序级别设置,比如系统恢复组件的能力?
-
如果定义了新的权限,请三思是否真的需要它们。可能已经有一个现成的权限可以涵盖你的使用场景。
组件级别
这里列出了一些你需要在策略中考虑的组件级别的问题。这些问题是你在每个组件上都应该问自己的问题:
-
你是否定义了所有组件?
-
如果你的应用程序使用了第三方库,你是否定义了你将使用的所有组件?
-
第三方库是否期望你的应用程序有特定的设置?
-
你是否希望这个组件对其他应用程序可见?
-
你需要添加一些意图过滤器吗?
-
如果组件不应该可见,你是否添加了意图过滤器?请记住,一旦你添加了意图过滤器,你的组件就会变得可见。
-
其他组件是否需要一些特殊权限来触发这个组件?
-
核对权限名称的拼写。
-
您的应用程序是否需要某些功能,如相机、蓝牙和短信?
概述
在本章中,我们学习了如何定义应用程序的策略文件。清单文件是应用程序最重要的工件,应当非常谨慎地定义。这个清单文件声明了应用程序请求的权限以及外部应用程序访问其组件所需的权限。通过策略文件,我们还定义了 APK 输出的存储位置以及应用程序将运行的最低 SDK 版本。策略文件公开了那些对应用程序不敏感的组件。在本章的末尾,我们讨论了开发者在编写清单文件时应该注意的一些示例问题。
本章节结束了本书第一部分的内容,我们学习了关于 Android 应用程序结构的知识。让我们继续学习本书的下一部分,重点关注用户数据的的安全存储。
第五章:尊重你的用户
既然我们已经清楚地理解了 Android 平台和应用程序安全框架及组件,让我们深入到安全最具挑战性的方面:数据保护。正如我之前所述,作为应用程序开发人员,你的信誉取决于你处理用户数据的安全程度。因此,本章的名称是:尊重你的用户!
本章构成了理解保护用户数据的重要性和意义的基础。本章从讨论评估数据安全性的基准和 CIA 三原则开始。接下来,我们以我们的书店应用程序为例,通过资产、威胁和攻击场景进行分析。我们讨论移动生态系统以及生态系统的不同组件如何影响用户数据的安全性。最后,我们将回顾 Android 的数字版权管理(DRM)框架。
数据安全原则
本节讨论数据安全的三个原则,即保密性、完整性和可用性,通常称为CIA 三原则。存储在设备或服务器上的任何数据都应满足这三个属性以确保安全。理解这些基准可以帮助我们评估我们的数据存储解决方案的安全性。这三个原则通常以 CIA 三合体的形式表达。
保密性
保密性是安全的第一支柱,它关注数据的隐私。这一原则确保私人数据远离窥探的目光,只对具有适当访问权限的用户可用。例如,Android 应用程序的私人数据应只对该应用程序的组件或其他具有适当权限的组件(如果数据受到权限保护)可访问。Linux 操作系统的沙盒和权限强制执行此保密性。在另一种情况下,可能存在包含敏感数据的加密文件在 SD 卡上。即使设备或 SD 卡遭到破坏,这些信息也不会泄露。这种保密性是通过密码学强制执行的。另一个保密性的例子是设备在一段时间不活动后自动锁定,需要用户凭据才能解锁。请注意,Linux 内核默认不支持文件系统加密,因此在存储之前对敏感数据进行加密对于安全至关重要。
完整性
数据完整性确保数据在传输过程中或静态存储时不会被故意或意外地更改或修改。例如,不恰当地写入数据库表可能会导致意外的完整性问题。因此,除非你对自己的技术非常了解,否则建议使用内置的同步方法来强制执行数据完整性。故意破坏数据完整性的一个例子可能是在应用程序与服务器通信的传输过程中发生。中间人可以监听数据并在其传输过程中进行修改。为了减轻这种欺诈行为,建议在通信时对数据进行加密,并使用安全套接字层(SSL)协议。为了额外的安全,可以使用校验和。SSL 还需要证书颁发机构(CA)的证书验证链,这在 Android 应用程序中很少使用。
可用性
数据可用性确保在需要时数据能够获得。我想补充一点,即数据应当在有适当权限的用户需要时可用。这非常重要,因为应用程序不应让未授权用户访问敏感信息,以可用性为名。
识别资产、威胁和攻击
没有绝对的安全。当我们谈论数据安全时,我们需要确定我们正在保护什么以及保护的对象是谁。以下三个问题可以帮助我们规划方法:
-
我们试图保护什么? 从 Android 应用程序的角度来看,我们是试图保护用户的用户名和密码,还是用户可能通过你的应用程序进行购买时输入的优惠码和信用卡号码,或者是用户使用你的应用程序购买的保护版权的歌曲或图片?通过回答这个问题,我们可以确定我们的资产。
-
我们试图保护资产免受谁的侵害? 换句话说,我们的威胁是什么?我们是否试图保护用户数据不受系统上其他应用程序的侵害,还是试图保护这些信息不受你开发的其他应用程序的侵害?即使设备被盗,我们也想要保护我们的资产吗?
-
什么是攻击? 回答这个问题有助于识别我们应用程序中的漏洞。我们要站在黑客的角度思考如何利用应用程序中的漏洞。
回答前面的三个问题将有助于我们确定资产的价值以及我们愿意在保护这些资产上投入的时间和精力。让我们尝试用一个示例应用程序来回答前面的问题。回到我们的书店应用程序,用户可以浏览目录中的书籍,将书籍添加到愿望清单中,并订购书籍以便寄送给用户。我们的应用程序会记住用户的基本信息,例如用户最后浏览的作者和类别以及语言和用户名,这样当用户登录时,应用程序会提供建议,让用户感到宾至如归。我们的应用程序还向用户提供商店的信用卡号、邮寄地址和姓名,以便用户准备好支付书籍费用时可以轻松结账。
让我们尝试回答第一个问题:我们试图保护什么?在前面示例中,我们的资产是:
-
姓名
-
信用卡号码
-
邮寄地址
-
最后搜索的作者
-
最后搜索的语言
-
最后搜索的类别
-
用户名
-
密码
-
书籍愿望清单
下图说明了我们示例中不同的敏感数据工件:
请注意,并非所有这些资产都需要同等程度地保护。存储机制应根据信息的敏感程度来决定。例如,信用卡号码和密码(如果存储在设备上)需要被强烈保护。您可以加密此类信息,并存储信息的哈希值,而不是以原始形式存储此信息。您将在传输过程中加密信息,并使用 SSL 协议进行安全通信。用户偏好(如语言、作者和类别)的丢失不会带来重大风险。即使这些信息丢失,用户也可以重新设置。
前面的分析还引发了关于 PII 的厚客户端和薄客户端的争论。厚客户端在设备本身上存储大量信息。因此,应用程序最终会在设备上存储 PII。薄客户端依赖后端服务器进行所有繁重的工作。它们在设备上存储最少的信息。这是一个好的方法,因为设备可能会丢失或被盗,然后用户数据的风险就会受到威胁。
接下来,我们要确定攻击场景。以下是一些示例场景的讨论。
假设用户安装了一个恶意应用程序。现在这个应用程序试图以不同的方式窃取用户信息。在第一种情况下,它试图访问不同的数据库表并提取用户信息。这是窃取私人信息的情况。如果数据库表受到权限保护,我们处于安全的位置。如果内容提供者检查组件的身份,我们将会处于更安全的情况。
在另一种情况下,恶意应用程序可能会发送带有不良数据的广播消息,接收应用程序可能会尝试对此采取行动,或者恶意应用程序可能会尝试启动其他应用程序的组件,并带有格式不良的数据,可能导致其他应用程序崩溃。因此,在采取行动之前,检查调用应用程序的身份并审查输入数据是非常重要的。
从这种攻击场景中我们可以得到的重要教训如下:
-
除非绝对必要,否则不要暴露组件。保持组件私有是我们的第一道防线。
-
如果我们暴露了一个组件,我们会确保我们用权限来保护它。这是决定是否希望将其暴露给整个系统还是仅暴露给由您创建的其他应用程序的好地方。如果用例是在同一作者编写的应用程序之间共享组件,我们可以定义自定义权限。
-
通过指定一些意图过滤器来减少攻击面。
-
在采取行动之前,请务必检查输入数据。如果数据不是所需的格式或形式,应该有一个优雅退出当前情况的计划。在这种情况下,向用户显示错误消息可以作为一个选项。
其他场景可能包括一个恶意应用程序监听通过流氓 Wi-Fi 连接的设备之间的数据交换。这个应用程序可以拦截信息,修改它,假装成用户正在连接的服务器,或者完全阻止数据流。所有这些场景都是安全风险。
另一个例子是,当恶意应用程序更改设备上存储的数据时。用户甚至可能没有意识到这些信息已经被更改。假设我们的应用程序在不同语言环境中本地化,并且用户设置了首选语言。在以下场景中,用户的首选语言从英语更改为例如日语。下次用户登录时,应用程序会以日语打开。在我们的案例中,安全风险并不大,这对用户来说是个烦恼,但这个例子证明了信息修改是另一种安全风险。
最后,我们需要考虑在发生安全漏洞时如何获取损失和我们的行动计划。如果信用卡信息、密码和社会安全号码等私人信息被窃取,这将是一个严重的安全风险。在发生安全漏洞时通知用户的计划必须仔细考虑。如果用户的偏好和愿望清单被不适当地访问,可能会引起用户的不满,但可能不会造成太大的隐私风险。
什么数据应该存储以及存储在哪里
之前的分析使我们意识到应用程序开发人员必须考虑的两个重要决策。
首先,应用程序开发者必须决定他/她想要从用户那里收集哪些信息。正如有最小权限原则一样,也有最少存储原则。最少存储原则导致风险和责任的最小化。应用程序开发者应始终尝试减轻个人识别信息(PII)的存储负担。在我们之前的例子中,应用程序可能不想存储信用卡详情、账单地址以及与支付相关的其他信息。支付是一个棘手的领域,而像 PayPal 这样的公司可以帮助用户处理结账过程。此外,任何处理信用卡号码的应用程序都建议遵循支付卡行业(PCI)标准。该标准列出了此类应用程序和服务器必须遵守的要求。我的建议是将此类操作交给最擅长这些事情的服务。
第二个需要深思的重要决定是用户数据存储在哪里。在当今分布式的数据存储环境中,开发者有许多存储选项,例如设备上、服务器上、云上或第三方应用程序。移动设备不应被视为安全的存储位置,部分原因是它可能容易被盗或丢失,而且大多数设备没有像台式机和笔记本电脑那样的高级安全机制,如安全元件和双启动。密码、加密密钥、大内容文件、个人识别信息(PII)和其他敏感数据应存储在后端服务器上。同样,重要的是要为这些服务器设置防火墙。
我们将在第七章《保护应用数据》中回到这个例子,根据前面的分析,我们将决定适当的存储选项和保护机制。
端到端的安全
大约十年前,我们把音乐存储在磁带和磁盘上;我们的照片放在相册里,我们只把电话用于紧急情况。快进到今天;我们的生活越来越多地走向数字化。我们的朋友、家人、喜好、不喜欢、照片、联系人列表,甚至我们的购买历史和信用卡号码都在数字化。想象一下用户丢失手机的场景。除了设备的价值和存储在其中的内容的情感价值,最大的风险是存储在设备上的用户个人信息的安全。这些信息可能包括可以识别个人的 PII,如姓名、社会安全号码、出生日期和母亲的婚前姓名。它还可能包括对密码、联系人列表和短信数据的访问。即使设备所有者拥有设备并且设备因恶意软件而受到威胁,这种风险仍然存在。
移动生态系统
如下图所示,移动生态系统中有不同的构件,如设备、网络、用户在设备上安装的应用程序、OEM(原始设备制造商)以及消费者的设备与之交互的其他服务。
让我们更深入地了解这些组件。
-
消费者:整个生态系统都围绕着消费者以及消费者如何与生态系统的不同部分互动。
-
设备制造商:也称为 OEM,这些公司生产设备的硬件。像 HTC、摩托罗拉、三星和 LG 这样的公司都设计和制造 Android 设备。除了设备的大小和样式,每个设备制造商都会加入他们自己的系统芯片(SOC)、设备驱动和固件,这些都会影响应用程序在不同设备上的工作方式。如果你在不同的设备上测试过你的应用程序,你可以轻易地注意到这些差异。硬件层面的任何安全缺陷都会影响使用该硬件的所有设备。硬件缺陷也很难修补。
-
操作系统供应商:Android 是一个开源操作系统,制造商可以自由修改它或使用自己的软件。例如,设备制造商可能会决定使用不同的 WebKit 引擎、音乐播放器或屏幕,而不是 Android 堆栈中捆绑的那个。这将导致应用程序在不同设备上的行为和外观有所不同。这些专有软件包中的安全缺陷可能导致你的应用程序受到威胁。运行特定版本操作系统的所有设备都会受到缺陷的影响。软件层面的缺陷通常可以修补,建议用户始终保持软件更新。
-
运营商:AT&T、Sprint、Verizon、Orange 和 Vodafone 等运营商提供了使移动设备真正移动的基础设施。他们为我们的设备提供数据和语音计划。他们还与设备制造商(在大多数情况下也是操作系统供应商)合作,将他们的定制应用程序捆绑在系统映像中。他们也可能与 OEM 合作,调整安全规则以满足他们的需求。例如,他们可能要求 OEM 直接加载和安装应用程序,而无需征得用户的同意或显示权限请求。
-
服务:这些是设备交互的服务,例如用于备份的云服务。在大多数情况下,用户会安装一个与后端交互的客户端。其他服务可能包括像 PayPal 这样的支付服务,像 Gmail 这样的邮件服务,以及像 Facebook 和 Twitter 这样的社交网络服务。这些服务大多数以第三方应用程序的形式提供给用户。
-
应用开发者:这是指个人应用开发者或小型开发者团队,他们将应用发布到如 Google Play 和亚马逊应用商店等应用商店。这类应用包括实用程序、游戏、内容消费应用等。本书的大部分读者属于这一类。
-
基础设施:这些技术和协议是移动基础设施的支柱。这包括CDMA(码分多址)、GSM(全球移动通信系统)、WiMAX(微波全球互操作性接入)、WAP(无线应用协议)以及如 NFC、RFID 和蓝牙等近场通信技术。这些技术中的安全漏洞可能导致我们的应用易受攻击。
-
标准和安全:在我们撰写这本书时,这两个移动生态系统的重要组成部分仍在不断发展中。
你可能已经注意到,移动生态系统中有许多参与者,从而增加了风险和威胁表面。此外,移动领域的一些主要玩家并不总是合作,有时甚至相互对抗,导致攻击模型复杂化。同时,制造商为特定市场生产设备。因此,这是一个复杂且不断变化的局面。从端到端的角度来看待安全问题,不难意识到应用开发者唯一能控制的也就是他们创建的应用。设备或操作系统中的任何其他缺陷也可能导致安全漏洞。例如,操作系统中的一个缺陷可能导致权限提升,让应用以 root 权限运行。在这种情况下,这个 root 应用可以访问设备上的所有信息。所有应用都会受到威胁,但如果开发者采用了良好的安全标准,他们的责任将最小化。
提示
应用开发者唯一能掌控的就是他们自己的应用。任何恶意用户都可以利用设备硬件、操作系统或运营商应用中的弱点,获取用户数据。
例如,我们的书店应用与数据库通信,向服务器发送信息并缓存一些数据。所有这些情况都需要得到保护。如果设备使用某种近场通信技术,如近场通信(NFC)、蓝牙或射频识别(RFID)来交换数据,了解这些技术相关的安全风险和新的附加场景是非常重要的。
第六章,你的工具 – 加密 APIs,讨论了可以用来保护传输中数据的加密算法。
数据的三个状态
让我们看看典型移动应用程序中的信息流。再次考虑书店应用程序。在我们的书店应用程序中,用户可以浏览目录中的书籍,将书籍添加到愿望清单中,并订购书籍以便邮寄给用户。我们的应用程序会记住用户的基本信息,例如用户最后浏览的作者和类别、用户的语言和用户名。用户的信用卡号、邮寄地址和姓名也会被存储以方便结账。
下图展示了一个可能的情况。书店应用程序在 Android 设备上使用 SQLite 数据库和平面文件作为缓存。应用程序在外部服务器上存储账户详情、书籍目录和愿望清单,并通过 Wi-Fi 连接到后端服务器。
在任何给定时刻,数据可以处于以下三种状态之一:在某个位置静止、从一个节点传输到另一个节点中、或在处理过程中。我们将这三种数据状态称为静止数据、传输中的数据和使用中的数据。让我们更详细地了解这三种状态:
-
静止数据:这是存储在某些存储介质上的数据,如 SD 卡、设备内存、后端服务器和数据库。此数据处于非活动状态。在上一个示例中,位于平面文件、SQLite 数据库表和后端服务器上的数据都被认为是静止数据。
-
使用中的数据:当前正在处理的数据称为使用中的数据。这类数据的例子包括从数据库表中访问的数据、通过意图发送到应用程序组件的数据,以及当前正在写入或从中读取的文件。
-
传输中的数据:当数据正在从一个节点传输到另一个节点时,称为传输中的数据。响应查询,将数据从数据库传输到应用程序是传输中数据的例子。
在处理数据和考虑端到端安全时,保护三种状态下的数据都是重要的。
数字版权管理
数字版权管理(DRM)是针对数字内容如音乐、电子书、应用程序、视频和电影等的访问控制技术。访问控制基于与内容相关联的权利对象。这个权利对象包含限制内容的使用、分发和复制的规则。开放移动联盟(OMA)开发了如 OMA DRM v1 和 OMA DRM v2 等 DRM 方案,但许多设备制造商也有自己的专有 DRM 方案。
DRM 系统包含以下组件:
-
内容服务器:这是设备从中获取媒体内容的服务器。
-
权利服务器:设备从中获取权利对象的的服务器。权利对象通常是一个带有与内容相关联的权限和约束的 XML 文件。
-
DRM 代理:该代理内置于设备中,是将内容和权限相关联并执行内容权限管理的可信实体。
-
存储设备:这是存储内容和权利对象的设备。它可以是手机或平板电脑,或者是外部存储,如 SD 卡,甚至是云存储。数字版权管理
你可以在www.openmobilealliance.org阅读关于 OMA DRM 的完整规范。OMA DRM 1.0 支持诸如内容锁定(内容不能转发到另一设备)、联合交付(内容和权利对象一起交付)以及分离交付(内容和权利对象从不同的服务器分别拉取)等模型。OMA DRM v2.0 的安全性基于 PKI,安全性显著提高。制造商可以选择支持其设备上的 DRM 方案。他们还可以根据需要实现或修改 DRM 方案。
安卓从 API 11 开始支持 DRM。安卓对 DRM 的支持是开放的,这样制造商可以选择自己的 DRM 代理。这是通过在两个架构层实现 DRM 框架来实现的。Android 开发者网站(developer.android.com)以下图解地展示了这一点:
DRM 管理器实现了 DRM 框架,对那些将他们选择的 DRM 代理与此框架集成作为插件的设备制造商来说很有兴趣。框架层抽象了 DRM 管理器的所有复杂性,并向开发者展示了一组统一的 API 以供使用。这些 API 在 Dalvik VM 中运行,与应用程序的其他代码一起。
所有 DRM API 都在android.drm
包中。这个包有类和接口来获取权利信息,关联内容与权利,查询 DRM 插件和 MIME 类型。DrmManager
类为每个DrmManagerClient
提供一个唯一的 ID 以便操作。
应用程序首先需要找出设备上可用的 DRM 插件。这可以通过使用DrmManagerClient
类来完成。
DrmManagerClient mDrmManagerClient = new DrmManagerClient(getContext());
String[] plugins = mDrmManagerClient.getAvailableDrmEngines();
下一步是向 DRM 服务器注册并下载权利对象。
DrmManagerClient mDrmManagerClient = new DrmManagerClient(context);
DrmInfoRequest infoRequest = new DrmInfoRequest(DrmInfoRequest.TYPE_RIGHTS_ACQUISITION_INFO, MIME);
mDrmManagerClient.acquireDrmInfo(infoRequest);
第三步是从权利对象中提取许可信息。这是通过使用DrmManager
的getConstraints
方法完成的。
ContentValues constraintsValues = mDrmManager.getConstraints(String path, int action);
ContentValues constraintsValues = mDrmManager.getConstraints(Uri uri, int action);
现在,我们需要将内容与权利对象关联。这是通过在DrmManager
的saveRights
方法中指定内容路径和权利路径来完成的。一旦完成这种关联,DRM 代理将继续执行内容的权限管理,无需用户干预。
int status = mDrmManager.saveRights(DrmRights drmRights, String rightsPath, String contentPath);
android.drm
包还提供了一些其他实用功能。查看此包以了解所有可用的功能(developer.android.com/reference/android/drm/package-summary.html
)。
本章总结
本章节介绍了数据安全的基础知识。我们讨论了数据安全的三个核心原则,即保密性、完整性和可用性。我们通过一个示例应用场景进行了演练,并尝试绘制我们的资产、威胁和攻击场景。我们试图评估安全漏洞相关的成本。我们的数据存储选项以及我们计划在保护数据上投入的时间、精力和资金将取决于这一分析。我们还反思了整个移动生态系统,以及在移动环境中端到端安全的意义。不难意识到,我们仅控制我们所编写的应用程序。我们以回顾 Android 的 DRM 框架和可用功能结束了这一章节。凭借所有这些关于数据安全的知识,让我们迈向下一章,学习应用程序开发人员可以使用的不同工具来保护用户数据。