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

> **For AI agents:** the complete documentation index is at [llms.txt](/docs/llms.txt). Append `.md` to any page URL for its markdown version.

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

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

<Tip>
  需要按用户进行身份验证？[JWT authentication](/cn/setup/jwt-authentication) 会通过您自己的登录系统保护文档，而不是使用共享密码，并支持按用户会话和基于组的页面访问控制。
</Tip>

<Note>
  在启用密码保护前，您需要先将 Jamdesk 项目[连接到 Git 仓库](/cn/setup/connecting-github)。配置存储在 `docs.json` 中，因此密码保护可以直接纳入常规构建和部署流程。
</Note>

## 应选择哪种模式？

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

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

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

## 保护整个站点

<Steps>
  <Step title="将 auth.password.enabled 添加到 docs.json">
    打开 `docs.json` 并声明全站保护。`hint` 字段为可选字段，但强烈建议设置，因为这是读者在屏幕上获取密码方式的唯一提示。

    ```json 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。

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

  <Step title="提交并推送">
    将更改推送到已配置的分支。Jamdesk 会运行构建，并在构建期间以全站模式启用密码保护。

    ```bash
    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](/images/password-protection/dashboard-pp-notset.webp)
  </Step>

  <Step title="在仪表板中设置密码">
    打开仪表板中的 **Project Settings**，然后滚动到 **Password Protection** 卡片。输入强密码（至少 8 个字符），然后点击 **Set password**。

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

    ![Password Protection card in On state showing rotate form, Revoke all sessions button, and disable instructions](/images/password-protection/dashboard-pp-on.webp)

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

  <Step title="验证保护机制">
    在隐私浏览器窗口中打开文档站点（或使用 `curl`），确认可以看到解锁页面。输入错误密码以检查错误状态，然后输入正确密码以进入站点。

    ```bash
    # 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 以用于下一个请求即可进入站点。
  </Step>
</Steps>

### 公开例外

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

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

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

```yaml
---
title: Get started
public: true
---
```

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

```json 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 和导航无法覆盖的内容，例如顶级落地页、动态生成的路由，或您不想重写的整个子树。

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

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

## 只保护少数页面

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

<Steps>
  <Step title="将页面标记为私有">
    在页面的 frontmatter 中添加 `private: true`。当页面所有者负责决定访问权限时，这是最简单的选项。

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

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

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

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

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

    ```bash
    git add content/runbook.mdx docs.json
    git commit -m "Gate the incident runbook"
    git push
    ```
  </Step>

  <Step title="设置密码">
    打开 **Project Settings**，找到 **Password Protection** 卡片，然后设置密码。卡片标题现在会显示 **On** 和 **Specific pages**，而不是 **Whole site**，并显示构建解析出的私有页面列表，方便您快速审核。

    ![Password Protection card in specific-pages mode with three private paths listed and updated disable instructions](/images/password-protection/dashboard-pp-specific.webp)
  </Step>

  <Step title="验证保护机制">
    正常浏览文档站点。公开页面应像以前一样加载，私有页面则应将您重定向到解锁页面。输入密码后，该设备会保持登录 30 天，之后无需再次输入密码即可阅读任何私有页面。
  </Step>
</Steps>

## 访客看到的内容

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

![ACME unlock screen with site name, lock icon, password field, and hint text below](/images/password-protection/unlock-screen.webp)

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

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

![Unlock screen after a failed attempt, with 'Incorrect password. Please try again.' in red](/images/password-protection/unlock-screen-error.webp)

访客输入正确密码后，会获得签名 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**。不会残留任何“休眠”状态。如果稍后重新启用保护，您需要设置新密码。

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

## 优先级规则

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

- 如果 `auth.password.enabled` 为 `true`，则整个站点都会受到保护。单个页面上的 `private: true` 将变得多余。
- 如果页面同时标记为 `public: true` 和 `private: 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 具有 `HttpOnly`、`Secure` 和 `SameSite=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`（或您的自定义域名）。

## 故障排除

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

<Accordion title="我设置了密码，但团队成员仍然看到解锁页面">
  对方的浏览器中可能保留着您轮换密码之前的旧 `jd_auth_<slug>` Cookie。您可以等待 30 天让 Cookie 过期，点击仪表板中的 **Revoke all sessions**，或让对方清除文档域名的 Cookie。对方下次访问时会收到输入当前密码的提示。
</Accordion>

<Accordion title="可以向不同组提供不同密码吗？">
  不能直接实现。Jamdesk 为每个站点使用一个共享密码。如果需要按组访问，请将文档拆分到多个项目中（每个项目使用独立密码），或使用特定页面模式，根据受众设置不同的公开和私有边界。
</Accordion>

<Accordion title="密码保护支持自定义域名和子路径代理吗？">
  两者都支持。解锁 Cookie 与主机绑定，因此每个主机（`*.jamdesk.app` 子域名和您的自定义域名）都需要独立进行身份验证。在一个主机上解锁的读者不会在另一个主机上预先完成身份验证。

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

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

<Accordion title="受保护的站点仍会出现在搜索引擎中吗？">
  不会。受保护站点会在解锁页面上设置 `noindex, nofollow`，并为每个受保护页面返回 `401`，因此搜索爬虫无法将保护范围内的内容编入索引。受保护站点中的公开页面仍会正常建立索引。
</Accordion>

## 下一步是什么？

<Columns cols={2}>
  <Card title="访问控制概览" icon="shield" href="/cn/setup/access-control">
    将密码保护与 SSO 以及多项目模式进行比较。
  </Card>
  <Card title="JWT 身份验证" icon="lock" href="/cn/setup/jwt-authentication">
    使用您自己的登录系统提供按用户会话，替代共享密码。
  </Card>
  <Card title="SSO（企业版）" icon="key" href="/cn/setup/sso">
    通过身份提供商实现按用户登录，替代共享密码。
  </Card>
  <Card title="自定义域名" icon="globe" href="/cn/deploy/custom-domains">
    在分享链接前，将文档放置到您自己的域名上。
  </Card>
  <Card title="auth.password 架构" icon="book" href="/cn/config/docs-json-reference#authpassword">
    查看 `enabled`、`hint`、`public` 和 `private` 的完整字段参考。
  </Card>
</Columns>