Jamdesk Documentation logo

密码保护

使用共享密码保护整个文档站点或指定页面。访客会看到解锁页面,其余文档仍保持公开。

有时,您希望文档继续存储在 Git 中并在线提供,但不对所有人可见。常见场景包括运行手册、预发布指南、仅限合作伙伴的文档和抢先体验功能。密码保护功能提供一个共享密码,可保护整个站点或指定页面,而无需将任何内容移出已有仓库。

屏幕截图显示的是英文界面。

需要按用户进行身份验证?JWT authentication 会通过您自己的登录系统保护文档,而不是使用共享密码,并支持按用户会话和基于组的页面访问控制。

在启用密码保护前,您需要先将 Jamdesk 项目连接到 Git 仓库。配置存储在 docs.json 中,因此密码保护可以直接纳入常规构建和部署流程。

应选择哪种模式?

Jamdesk 提供两种密码保护模式。请根据哪些内容公开、哪些内容不公开来选择。

Whole-site modeSpecific-pages mode
Use when所有内容都是私有的:内部工程文档、公共站点的预发布副本、尚未发布的产品。大多数文档都是公开的,只需隐藏少数页面(例如运行手册、Beta 功能或内部 API 参考)。
How you turn it ondocs.json 中设置 auth.password.enabled: true在 frontmatter 中将页面标记为 private: true,或在 auth.password.private[] 下列出路径。
Public exceptions支持:可将单个页面、导航组或 glob 模式标记为公开。不适用。除非标记为私有,否则所有页面均为公开。

两种模式共用同一个仪表板卡片、解锁页面以及轮换和撤销控制项。您可以随时编辑 docs.json 并推送,以便在两种模式之间切换。

保护整个站点

1
将 auth.password.enabled 添加到 docs.json

打开 docs.json 并声明全站保护。hint 字段为可选字段,但强烈建议设置,因为这是读者在屏幕上获取密码方式的唯一提示。

docs.json
{
  "$schema": "https://jamdesk.com/docs.json",
  "name": "Acme Docs",
  "theme": "jam",
  "auth": {
    "password": {
      "enabled": true,
      "hint": "Ask #docs-access on Slack"
    }
  }
}

提示为纯文本,最多 200 个字符,不能包含 HTML。

不要将密码本身放入 docs.json。构建完成后,您可以在仪表板中设置密码。仓库中只包含启用标记和可选提示。

2
提交并推送

将更改推送到已配置的分支。Jamdesk 会运行构建,并在构建期间以全站模式启用密码保护。

git add docs.json
git commit -m "Turn on password protection"
git push

构建完成后,仪表板卡片会从 Off 变为 Password not set,站点会为每个页面返回 401。在设置密码之前,所有请求都会被拒绝。

Password Protection card showing 'Password not set' state with warning alert and Set password button

3
在仪表板中设置密码

打开仪表板中的 Project Settings,然后滚动到 Password Protection 卡片。输入强密码(至少 8 个字符),然后点击 Set password

卡片会切换到 On 状态。拥有密码的用户现在可以浏览站点,其他用户则会看到解锁页面。

Password Protection card in On state showing rotate form, Revoke all sessions button, and disable instructions

Jamdesk 从不存储明文密码。密码会使用 scrypt 进行哈希处理并存储在仪表板数据库中,绝不会写入仓库或 docs.json。这也意味着如果您忘记密码,Jamdesk 无法通过电子邮件发送给您。请改用轮换密码。

4
验证保护机制

在隐私浏览器窗口中打开文档站点(或使用 curl),确认可以看到解锁页面。输入错误密码以检查错误状态,然后输入正确密码以进入站点。

# Should respond with HTTP/1.1 401 and the unlock HTML
curl -I https://acme.jamdesk.app/

# Submit the password. On success, sets the jd_auth_<slug> cookie.
curl -i -X POST https://acme.jamdesk.app/jd/unlock \
  -d "password=your-passphrase&from=/"

成功解锁后会返回 303 重定向,并包含 Set-Cookie: jd_auth_acme=...; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000 标头。保存此 Cookie 以用于下一个请求即可进入站点。

公开例外

全站模式提供一个例外机制:即使站点其余部分受到保护,您仍可以让特定页面保持公开。这样,您可以在私有文档旁边发布营销落地页或注册表单。

您有三种方式可以将页面标记为公开,它们都会在每次构建时合并到同一个允许列表中。

Frontmatter 是粒度最细的选项。在任意 .mdx 文件中添加 public: true,只有该页面会绕过保护:

---
title: Get started
public: true
---

导航组 可以一次覆盖整个部分。在 docs.json 的导航中将 public: true 设置在 grouptab 上,该项下的所有页面都会公开。适用于在私有工程文档旁边放置一个名为“Marketing”的标签页:

docs.json
{
  "navigation": {
    "tabs": [
      {
        "tab": "Marketing",
        "public": true,
        "groups": [
          {
            "group": "Overview",
            "pages": ["landing", "pricing", "changelog"]
          }
        ]
      },
      {
        "tab": "Internal",
        "groups": [
          { "group": "Runbooks", "pages": ["deploys", "oncall"] }
        ]
      }
    ]
  }
}

auth.password.public[] 下的显式 glob 可处理 frontmatter 和导航无法覆盖的内容,例如顶级落地页、动态生成的路由,或您不想重写的整个子树。

docs.json
{
  "auth": {
    "password": {
      "enabled": true,
      "hint": "Ask #docs-access on Slack",
      "public": [
        "/landing",
        "/pricing",
        "/marketing/**",
        "/blog/*"
      ]
    }
  }
}

Glob 支持 *(一个路径段)和 **(任意深度)。验证时会拒绝单独的 /:如果 Jamdesk 接受该值,一个简单的拼写错误就可能静默解锁整个站点。每次构建后,仪表板卡片都会显示解析后的允许列表,您可以检查构建实际采用了哪些配置。

只保护少数页面

特定页面模式采用相反的流程:默认所有内容公开,您可以选择将单个页面加入保护范围。

1
将页面标记为私有

在页面的 frontmatter 中添加 private: true。当页面所有者负责决定访问权限时,这是最简单的选项。

---
title: Incident Runbook
description: What to do when the deploys dashboard is on fire.
private: true
---

如果您希望将受保护路径集中保存在一个文件中,也可以在 docs.jsonauth.password.private[] 下添加这些路径。两种方式会叠加,因此可以混合使用。

docs.json
{
  "auth": {
    "password": {
      "hint": "Ask the on-call engineer",
      "private": ["/admin/runbook", "/internal/api-keys"]
    }
  }
}

请注意,这里没有 enabled: true。不设置 enabled,而直接配置 auth.password.private[],会自动启用特定页面模式。

2
提交并推送

推送更改。下一次构建会检测私有页面,以特定页面模式启用保护,并显示设置密码的仪表板提示,过程与全站模式完全相同。

git add content/runbook.mdx docs.json
git commit -m "Gate the incident runbook"
git push
3
设置密码

打开 Project Settings,找到 Password Protection 卡片,然后设置密码。卡片标题现在会显示 OnSpecific pages,而不是 Whole site,并显示构建解析出的私有页面列表,方便您快速审核。

Password Protection card in specific-pages mode with three private paths listed and updated disable instructions

4
验证保护机制

正常浏览文档站点。公开页面应像以前一样加载,私有页面则应将您重定向到解锁页面。输入密码后,该设备会保持登录 30 天,之后无需再次输入密码即可阅读任何私有页面。

访客看到的内容

当用户访问受保护的页面时,会看到居中的解锁卡片。卡片只显示站点名称和可选提示,不显示侧边栏或导航。

ACME unlock screen with site name, lock icon, password field, and hint text below

卡片会使用 docs.json 中配置的站点徽标和主色进行品牌化。密码字段支持显示切换,并会自动获得焦点。

输入错误密码时,卡片会显示错误消息和新的输入框,并在尝试之间加入短暂延迟。错误密码请求和未提供密码的请求都会进入同一个页面,因此页面不会透露“密码错误”还是“尚未输入密码”。

Unlock screen after a failed attempt, with 'Incorrect password. Please try again.' in red

访客输入正确密码后,会获得签名 Cookie,并可正常浏览,直到会话过期或您撤销会话。

轮换和撤销会话

共享密码最终需要更改,例如密码被转发给他人后,或团队成员离开团队时。

打开 Password Protection 卡片,在 Rotate password 字段中输入新密码,然后点击 Save new password。使用旧密码的用户将在下一次请求时被拒绝,使用新密码的用户可以正常进入。轮换会立即生效,无需重新构建。

如果您只想强制所有活跃会话退出,而不更改密码(例如某人的笔记本电脑丢失),请改为点击 Revoke all sessions。此操作会递增服务器端版本计数器,使递增前签发的所有 Cookie 失效。访客重新输入当前密码后即可再次进入。

禁用保护

保护由 docs.json 驱动,因此关闭保护意味着编辑文件并推送。

  • **Whole site:**移除 auth.password.enabled(或将其设置为 false)。
  • **Specific pages:**移除所有 private: true 标记,并清空 auth.password.private

下一次构建时,Jamdesk 会删除存储的密码哈希,并将卡片切回 Off。不会残留任何“休眠”状态。如果稍后重新启用保护,您需要设置新密码。

您的源代码仓库并未受到密码保护。密码保护只保护托管在 *.jamdesk.app(或您的自定义域名)上的文档站点。如果您的 GitHub 仓库是公开的,MDX 内容仍然可以在那里读取。如果需要完整的内容保护,请将仓库设为私有。

优先级规则

单个页面可能同时受到多个信号影响。解析顺序如下,从最具体到最不具体:

  • 如果 auth.password.enabledtrue,则整个站点都会受到保护。单个页面上的 private: true 将变得多余。
  • 如果页面同时标记为 public: trueprivate: true,则公开优先。更安全的默认行为是不意外泄露页面。
  • Frontmatter 中的 public: true、导航组中的 public: true 以及 auth.password.public[] glob 会合并到同一个允许列表中。不存在“最具体者优先”规则。只要任一信号将页面标记为公开,该页面就是公开的。
  • 如果设置了 auth.password.private[] 但未设置 auth.password.enabled,Jamdesk 会自动启用特定页面模式。您无需执行其他操作。

会话和速率限制的工作方式

本节介绍会话 Cookie、速率限制和密码存储。

会话 Cookie。 成功解锁后,Jamdesk 会设置名为 jd_auth_<slug> 的 Cookie(例如 jd_auth_acme)。该 Cookie 具有 HttpOnlySecureSameSite=Lax 属性,作用域限定为当前主机,并使用 HMAC-SHA256 签名。其负载包含项目 slug、当前版本计数器和过期时间戳,因此任何篡改都会导致验证失败。默认有效期为 30 天,每次成功解锁时都会刷新。

速率限制。 解锁端点每小时应用两个计数器:每个 IP 10 次尝试每个项目 100 次尝试。两项限制都会在 scrypt 哈希检查之前执行,因此暴力破解尝试无法消耗 CPU 或泄露时间信息。达到任一限制后,会返回带有 Retry-After 标头的 429 Too Many Requests

存储。 您的密码会使用 scrypt 进行哈希处理,并存储在仪表板的 Firestore 中。密码不会写入仓库、docs.json 或构建产物。如果丢失密码,请进行轮换。不提供恢复路径。

本地开发期间测试

jamdesk dev 会使用实时 R2 内容和实时配置运行文档。本地开发服务器不会执行密码保护,因此您无需知道密码即可预览受保护页面。这是有意设计的:您是作者,已经拥有仓库的访问权限,而让本地预览受到密码墙阻挡只会增加操作阻力,却不会带来安全收益。

如果要验证实际保护机制,请在尚未拥有 Cookie 的浏览器窗口中访问已部署站点 <slug>.jamdesk.app(或您的自定义域名)。

故障排除

仪表板卡片可能显示 Password not set。只有在完成以下两项操作后,保护才会启用:(1) 将配置推送到 docs.json;(2) 在 Project Settings 中设置密码。在第二步完成之前,每个请求都会返回 401,响应正文为解锁页面。如果您期待的是某个特定目标页面,可能会误以为页面“没有显示”。

对方的浏览器中可能保留着您轮换密码之前的旧 jd_auth_<slug> Cookie。您可以等待 30 天让 Cookie 过期,点击仪表板中的 Revoke all sessions,或让对方清除文档域名的 Cookie。对方下次访问时会收到输入当前密码的提示。

不能直接实现。Jamdesk 为每个站点使用一个共享密码。如果需要按组访问,请将文档拆分到多个项目中(每个项目使用独立密码),或使用特定页面模式,根据受众设置不同的公开和私有边界。

两者都支持。解锁 Cookie 与主机绑定,因此每个主机(*.jamdesk.app 子域名和您的自定义域名)都需要独立进行身份验证。在一个主机上解锁的读者不会在另一个主机上预先完成身份验证。

子路径设置(通过您自己的代理将文档放在 yoursite.com/docs 下)开箱即用:解锁表单会在 /_jd/ 路径前缀下提交,而每个已记录的代理配置都会转发该路径。启用或禁用密码保护时无需修改代理配置。

如果您的代理是在设置指南加入 /_jd/ 转发之前配置的,请将 /_jd/* 添加到其转发路径中。

不会。受保护站点会在解锁页面上设置 noindex, nofollow,并为每个受保护页面返回 401,因此搜索爬虫无法将保护范围内的内容编入索引。受保护站点中的公开页面仍会正常建立索引。

下一步是什么?

访问控制概览

将密码保护与 SSO 以及多项目模式进行比较。

JWT 身份验证

使用您自己的登录系统提供按用户会话,替代共享密码。

SSO(企业版)

通过身份提供商实现按用户登录,替代共享密码。

自定义域名

在分享链接前,将文档放置到您自己的域名上。

auth.password 架构

查看 enabledhintpublicprivate 的完整字段参考。