记得在笨鸟海淘做仓库管理系统的时候,我用 Windows 写仓库管理软件。当时系统用的是 Windows 10。微软的官网对 UWP 吹得神乎其神,各种设备兼容,一套代码多种设备运行云云,很动人。最后,在功能快完成的时候,掉坑了。APP 没有打印的权限。那是我第一次掉进 APP 沙盒的坑。
最近一次掉沙盒的坑是上周,写 PNG Zip (一个 PNG 压缩软件,App Store 下载链接)的时候。起初,我觉得一周的时间应该能完成所有的工作。最终,我花了一周半的时间。
所以,我觉得我应该跟你分享一下这个掉坑和填坑的过程。看完这篇文章,你应该对沙盒机制有了一定程度的了解,如何在沙盒机制下编程,最后我还会给你一些建议。
在说掉坑填坑之前,我们先了解一下,沙盒是什么。
在我们日常用到的操作系统中,应用沙盒机制已经普遍存在了。如 iOS、Android、macOS、Windows。由于手机是我们每天都用的贴身工具,里面保存了非常多的隐私数据。所以手机操作系统中,所有应用程序都是沙盒隔离的。而 PC 系统在这两年也开始推广沙盒的应用,如 macOS、Windows 10。今天我要讲的是 macOS 的沙盒。
我们先来看看沙盒的定义。在 Wikipedia 中,沙盒是这么定义的。
In computer security, a sandbox is a security mechanism for separating running programs, usually in an effort to mitigate system failures or software vulnerabilities from spreading. It is often used to execute untested or untrusted programs or code, possibly from unverified or untrusted third parties, suppliers, users or websites, without risking harm to the host machine or operating system.
在计算机安全中,沙盒是一种隔离应用程序的机制,通常致力于抑制系统故障或者软件故障的扩散。它通常用于执行未测试的或者不受信任的,来自于未认证或者不受信任的第三方、供应商、用户或者网站的应用程序和代码,以免主机和操作系统受到破坏。
在 macOS 中,沙盒是这样的。
App Sandbox is an access control technology provided in macOS, enforced at the kernel level. It is designed to contain damage to the system and the user ’ s data if an app becomes compromised.
应用沙盒是一种由 macOS 提供的内核级别的访问控制技术。它是为了控制系统或者用户数据受到的破坏而设计的(当一个应用程序受到劫持的时候)。
这两个定义说的意思差不多,就是对应用进行隔离。包括运行环境和用户数据。具体就是说,第一次打开应用,你上不了网,你拍不了照片,你写不了文章。所有的一切你都需要用户授权。
答案是安全。现如今应用变得非常的复杂。而复杂的系统,不管你多么努力,制定多么严格的安全编程规范,测试得多么地充分,攻击者总能找到漏洞。沙盒的作用就是当你的应用受到攻击时,系统和用户数据受到的破坏能被控制在一定的范围内。
在沙盒环境下,我们需要做一些权限相关的额外工作。
在 macOS 中,通过Entitlements来声明权限。创建 xcode 工程时,xcode 会默认创建appname.entitlements的 plist 文件。开发者可以编辑这个文件,或者通过 target 的 Capabilities 标签配置。
在 Capabilities 只能声明部分权限,有些权限需要手工编辑.entitlement 文件。
声明了权限后,应用就可以使用相应的编程接口来实现所要的功能。例如,在用户 Home 目录读写文件。你可以通过 NSFileManager 提供的 API 来获取相应的路径。
swift
class NSFileManager
var homeDirectoryForCurrentUser: URL { get }
Objective-C
interface NSFileManager
@property(readonly, copy) NSURL *homeDirectoryForCurrentUser;
对于用户 Home 目录以外的文件,需要唤起NSOpenPanel来让用户选择,或者通过拖拽的方式将文件拖拽到应用界面中。通过阅读Introduction to Drag and Drop,你可以很快地实现拖拽功能。
通过用户选择或者拖拽获取到的文件读写权限在应用关闭之后就会丢失。所以,需要一种方法来保存这个权限。macOS 给我们提供了Security Scope Bookmark。NSURL 提供了对应的接口来创建Security Scope Bookmark。
swift
class NSURL
func bookmarkData(options: NSURL.BookmarkCreationOptions = [],
includingResourceValuesForKeys keys: [URLResourceKey]?,
relativeTo relativeURL: URL?) throws -> Data
Objective-C
interface NSURL
- (NSData *)bookmarkDataWithOptions:(NSURLBookmarkCreationOptions)options
includingResourceValuesForKeys:(NSArray<NSURLResourceKey> *)keys
relativeToURL:(NSURL *)relativeURL
error:(NSError * _Nullable *)error;
Security Scope Bookmark以 NSData 的对象表示。NSData 可以持久化到文件,也可以通过 IPC 等方式发送给其他进程。在应用重启或者另一个进程接收到 NSData 对象后,即可重新获取权限。如果想要持久化 bookmark,可以使用 NSData 的 writeToURL 方法。
swift
class NSData
func write(to url: URL,
atomically: Bool) -> Bool
Objective-C
interface NSData
- (BOOL)writeToURL:(NSURL *)url
atomically:(BOOL)atomically;
Security Scope Bookmark中保存了 NSURL 的相关信息。我们可以通过 NSURL 提供的接口重新获得保存的 NSURL 对象。
swift
class NSURL
convenience init(resolvingBookmarkData bookmarkData: Data,
options: NSURL.BookmarkResolutionOptions = [],
relativeTo relativeURL: URL?,
bookmarkDataIsStale isStale: UnsafeMutablePointer<ObjCBool>?) throws
Objective-C
interface NSURL
+ (instancetype)URLByResolvingBookmarkData:(NSData *)bookmarkData
options:(NSURLBookmarkResolutionOptions)options
relativeToURL:(NSURL *)relativeURL
bookmarkDataIsStale:(BOOL *)isStale
error:(NSError * _Nullable *)error;
通过 NSURL 重新获取权限。
swift
class NSURL
func startAccessingSecurityScopedResource() -> Bool
Objective-C
interface NSURL
- (BOOL)startAccessingSecurityScopedResource;
一旦不再需要使用权限,尽快释放权限。
swift
class NSURL
func stopAccessingSecurityScopedResource()
Objective-C
interface NSURL
- (BOOL)stopAccessingSecurityScopedResource;
人在江湖飘,哪能不挨刀。掉坑总是难免的。NSURL 提供了一个很容易让人误用的接口。当我第一眼看到这个接口时,我就把它当作持久化 bookmark 的接口了。而且这个接口也确实能持久化 bookmark 数据。然而当你重启应用重新得到 NSURL 对象后会发现,无法获取权限。
swift
class NSURL
class func writeBookmarkData(_ bookmarkData: Data,
to bookmarkFileURL: URL,
options: NSURL.BookmarkFileCreationOptions) throws
Objective-C
interface NSURL
+ (BOOL)writeBookmarkData:(NSData *)bookmarkData
toURL:(NSURL *)bookmarkFileURL
options:(NSURLBookmarkFileCreationOptions)options
error:(NSError * _Nullable *)error;
实际上,这个接口是用于创建别名链接的。
作为开发者,出于降低项目风险和减少工作量的目的。我的建议是尽可能禁用沙盒机制。因为沙盒这个机制还处于发展阶段。权限控制的粒度和编程接口都还没稳定。
可以不开启的场景
必须开启沙盒的场景
应用沙盒提供了一种隔离机制,使得被劫持的应用能够造成的破坏被限制在一定的范围内。同时,沙盒机制是一把双刃剑。他在提供保护的同时,也加重了开发者的负担,产生额外的辅助数据,占用磁盘空间。甚至在沙盒环境下,部分功能无法实现。
说到底,这是个信任问题。软件之间、系统之间、人与人之间需要一种信任。当信任缺失的时候,所有参与到其中的任何一方都要付出代价。
分享
如果你觉得这篇文章写得不错,请分享给你的朋友。
反馈
如果你有不明白的地方,如果你有建设性的建议,如果你想我写某个主题的文章,随时丢个邮件给我。我的邮箱地址是 cykit@qq.com 。
圣诞节促销
原文链接
http://www.jianshu.com/p/403593d433f1
一个爱跳坑的码农。十年来跳了无数的坑。有些坑填平了,有些坑越过去了,有些坑却永远也无法过去。
关注公众号,接收 [跳坑的码农] 最新动态。
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.