发布于 2026-01-06 1 阅读
0

更好的 Deno 安全性:在运行时请求权限 DEV 的全球展示挑战赛(由 Mux 呈现):推介你的项目!

更好的 Deno 安全性:在运行时请求权限

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

Deno 是服务器端 TypeScript 和 JavaScript 领域的新秀,它默认具备安全性。这一点显而易见。他们在文档和会议演讲中反复强调这一点,确保用户了解。他们的主页上也提到了这一点,前三句话就提到了

我很高兴他们选择了默认安全模式。但我不太喜欢他们用来演示安全性的示例。这些示例宣扬了一种理念:你应该预先指定应用程序的权限。这让我想起了早期的安卓模式,当时你必须在安装应用程序时授予其所有权限。最终,安卓系统改进了这一点,采用了类似 iOS 和浏览器使用的更优模式:让程序在运行时请求必要的权限,以便用户能够根据上下文响应请求。

让我们来看一下Deno v1 发布会上的示例代码:

import { serve } from "https://deno.land/std@0.50.0/http/server.ts";

for await (const req of serve({ port: 8000 })) {
  req.respond({ body: "Hello World\n" });
}
Enter fullscreen mode Exit fullscreen mode

我已经将此示例复制到 GitHub 仓库中,您可以直接运行它:

$ deno run https://raw.githubusercontent.com/BMorearty/deno-permissions-samples/master/serve-original.ts
Enter fullscreen mode Exit fullscreen mode

你会发现它失败了,这是理所当然的,因为它没有网络权限。v1 版本页面对此解释道:“除非--allow-net提供命令行标志,否则上述示例将失败。”

Deno网络权限错误

通过运行以下命令修复--allow-net

$ deno run --allow-net https://raw.githubusercontent.com/BMorearty/deno-permissions-samples/master/serve-original.ts
Enter fullscreen mode Exit fullscreen mode

(由于它正在静默等待端口,因此不会显示任何输出。)

听起来不错,也很安全。但是,当用户下载并运行脚本,或者像上面的例子那样直接从互联网运行脚本时,他们事先并不知道程序为什么需要网络权限——更糟糕的是,文件权限。因此,他们会在运行程序之前盲目地授予它所需的所有权限。

这和以前安装安卓应用时需要的操作类似。我以前用安卓手机的时候就觉得很麻烦。有时候我想用某个应用,但又不想授予它访问权限,比如我的联系人。Deno 的一个改进之处在于,至少在 Deno 里,权限不是非此即彼的,而是由开发者自行选择授予哪些权限。

更好的选择:在运行时请求权限。

值得庆幸的是,Deno 已经提供了一种比这更好的安全模型,只是没有得到大力推广。该程序可以在需要时请求权限,而不是要求用户预先在命令行中授予权限。

// serve-request.ts
import { serve } from "https://deno.land/std@0.50.0/http/server.ts";

const netPermission = await Deno.permissions.request({ name: "net" });
if (netPermission.state === "granted") {
  for await (const req of serve({ port: 8000 })) {
    req.respond({ body: "Hello World\n" });
  }
} else {
  console.log("Can’t serve pages without net permission.");
}
Enter fullscreen mode Exit fullscreen mode

截至撰写本文时,Deno.permissions该功能尚未包含在稳定版 API 中,因此您需要使用以下命令运行它--unstable

deno run --unstable https://raw.githubusercontent.com/BMorearty/deno-permissions-samples/master/serve-request.ts
Enter fullscreen mode Exit fullscreen mode

以下是授予权限后的样子(我输入g并按下了回车键Enter):

在运行时请求权限

以下是否认时的样子:

运行时拒绝权限

“Deno requests”这行代码是Deno本身的一部分,并非由应用程序控制。如上面的代码所示,“Can't serve pages without net permission”这行代码是应用程序的自定义响应。应用程序可以根据用户选择不授予权限的情况,以任何方式做出响应。

不必担心 Deno 会请求已经授予的权限。如果用户以默认方式运行应用--allow-netDeno.permissions.request则不会重复请求权限,而是会{ "state": "granted" }立即返回。

如果应用在运行时多次请求相同的权限,情况也是如此。如果该权限在运行时已被授予或拒绝过一次,则所有后续权限请求都会记住该响应。

// request-twice.ts
const netPermission1 = await Deno.permissions.request({ name: "net" });
console.log(
  `The first permission request returned ${JSON.stringify(netPermission1)}`,
);
const netPermission2 = await Deno.permissions.request({ name: "net" });
console.log(
  `The second permission request returned ${JSON.stringify(netPermission2)}`,
);
Enter fullscreen mode Exit fullscreen mode

跑步:

$ deno run --unstable https://raw.githubusercontent.com/BMorearty/deno-permissions-samples/master/request-twice.ts
Enter fullscreen mode Exit fullscreen mode

如您所见,它只请求一次权限:

请求两次权限

请求许可时请提供背景信息

iOS人机界面指南中关于权限的规定如下:

请解释您的应用为何需要这些信息。请提供自定义文本(称为用途字符串或使用说明字符串),以便在系统的权限请求提示中显示,并附上示例。文本应简洁明了,使用句子大小写,并保持礼貌,以免用户感到压力。无需包含您的应用名称——系统会自动识别您的应用。

这条建议对 Deno 应用也同样适用。如果你能向用户解释为什么需要权限,他们就更有可能授予权限:

// serve-context.ts
import { serve } from "https://deno.land/std@0.50.0/http/server.ts";

let netPermission = await Deno.permissions.query({ name: "net" });
if (netPermission.state === "prompt") {
  console.log("Net permission needed to serve web pages.");
  netPermission = await Deno.permissions.request({ name: "net" });
}
if (netPermission.state === "granted") {
  for await (const req of serve({ port: 8000 })) {
    req.respond({ body: "Hello World\n" });
  }
} else {
  console.log("Can’t serve pages without net permission.");
}
Enter fullscreen mode Exit fullscreen mode

此版本会在发出请求之前打印请求原因。这为用户提供了足够的上下文信息,以便他们了解 Deno 请求权限的原因。

但是这个调用是做什么用的呢Deno.permissions.query?既然我们在显示权限提示之前要先显示一些上下文信息,那么首先需要检查应用是否已经拥有权限。否则,显示权限上下文信息就毫无意义了。

Deno.permissions.query可以返回三种可能的状态:

  1. { "state": "granted" }意味着你已经获得许可。
  2. { "state": "denied" }这意味着您的权限已被拒绝。请求权限毫无意义,因为它会立即返回“已拒绝”,而不会显示任何提示。
  3. { "state": "prompt" }意思是,如果您调用该命令request,则会向用户显示提示。

让我们运行一下:

deno run --unstable https://raw.githubusercontent.com/BMorearty/deno-permissions-samples/master/serve-context.ts
Enter fullscreen mode Exit fullscreen mode

然后你会看到提示:

请求权限前请提供背景信息

如果你运行它--allow-net,你会发现它没有显示上下文,因为对的调用Deno.permissions.query表明对的调用Deno.permissions.request将返回成功。

我认为这是处理代码权限的最佳方法。

愿望清单:持久保存已安装脚本的权限。

Deno 提供了一个deno install命令,允许您将 Deno 脚本添加到您的机器中:

$ deno install --unstable https://raw.githubusercontent.com/BMorearty/deno-permissions-samples/master/serve-context.ts
Enter fullscreen mode Exit fullscreen mode

此操作会下载、编译并缓存脚本及其依赖项。它还会创建一个可执行脚本。在 Mac 上,它存储在~/.deno/bin/serve-context(您可以将~/.deno/bin其添加到 PATH 环境变量中),其结构如下所示:

#!/bin/sh
# generated by deno install
deno "run" "--unstable" "https://raw.githubusercontent.com/BMorearty/deno-permissions-samples/master/serve-context.ts" "$@"
Enter fullscreen mode Exit fullscreen mode

注意deno install您传递给 ` --unstableget_ ...deno rundeno install --allow-net https://my/script--allow-net

再次强调,要求用户预先决定所有权限并非理想之选。但 Deno 并不会持久保存权限。安装后,每次运行 Deno 时,它都会请求网络权限serve-context--allow-net

我希望Deno能改进一下,允许它将用户对每个应用权限问题的回答进行本地缓存。浏览器在域请求权限(例如相机或地理位置权限)时就是这么做的。当然,还需要一种方法来事后撤销或授予权限。

愿望清单:让脚本将原因传递给request调用

在这个serve-context例子中,我们必须:

  1. 打电话Deno.permissions.query询问是否已获得许可。
  2. 如果不:
    1. 显示脚本需要权限的原因
    2. 请用以下方式请求Deno.permissions.request

如果所有操作都能通过一次调用完成,那就简单多了Deno.permissions.request我认为 Deno 应该允许脚本reason向权限请求传递一个简短的字符串。如果脚本尚未获得权限,则在询问用户是否授予权限之前,会显示权限不足的原因。如果脚本已经拥有权限,则不会显示权限不足的原因。

如果 Deno 支持此功能,步骤将简化为:

  1. 请求权限Deno.permissions.request

以下是代码示例(由于reason当前传递的键无效,因此无法运行request):

// serve-reason.ts
import { serve } from "https://deno.land/std@0.50.0/http/server.ts";

const netPermission = await Deno.permissions.request(
  { name: "net", reason: "Net permission needed to serve web pages." },
);
if (netPermission.state === "granted") {
  for await (const req of serve({ port: 8000 })) {
    req.respond({ body: "Hello World\n" });
  }
} else {
  console.log("Can’t serve pages without net permission.");
}
Enter fullscreen mode Exit fullscreen mode

参考

文章来源:https://dev.to/bmorearty/better-deno-security-ask-for-permission-at-runtime-1fnm