---
title: 密码保护
description: 了解如何为整个文档站点或指定页面设置共享密码并控制访客访问。
---

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

有时，您希望文档保留在 Git 中并在线提供，但不对所有人公开。常见场景包括操作手册、预发布指南、仅供合作伙伴查看的文档，以及抢先体验功能。密码保护功能使用一个共享密码，为整个站点或指定页面设置访问门槛，无需将任何内容移出您现有的代码仓库。

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

<Tip>
  需要按用户进行身份验证？[JWT authentication](https://jamdesk.com/docs/cn/setup/jwt-authentication) 可通过您自己的登录系统为文档设置访问门槛，而非使用共享密码，并支持按用户区分会话以及基于用户组的页面访问权限。
</Tip>

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

## 应选择哪种模式？

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

| | **整个站点模式** | **指定页面模式** |
|---|---|---|
| **适用场景** | 所有内容都需要保密：内部工程文档、公开站点的预发布副本、尚未发布的产品。 | 大多数文档公开，只需隐藏少数页面（例如操作手册、Beta 功能或内部 API 参考）。 |
| **启用方式** | 在 `docs.json` 中设置 `auth.password.enabled: true`。 | 在 frontmatter 中将页面标记为 `private: true`，或在 `auth.password.private[]` 下列出路径。 |
| **公开例外** | 可以：将单个页面、导航组或 glob 模式标记为公开。 | 不适用。除非标记为私有，否则所有页面都公开。 |

两种模式共用同一张仪表板卡片、同一个解锁屏幕，以及相同的轮换和撤销控件。您可以随时编辑 `docs.json` 并推送更改，在两种模式之间切换。

## 保护整个站点

### 1. 在 docs.json 中添加 auth.password.enabled

打开 `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>

### 2. 提交并推送

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

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

构建完成后，仪表板卡片会从 **Off** 变为 **Password not set**，站点也会被锁定。每个页面都会返回 `401` 并显示密码屏幕；在您设置密码之前，任何密码都无法通过验证。您标记为公开的页面仍可访问，项目 API 密钥也仍可用于 Search API 和 MCP。如需重新公开整个站点，请将 `auth.password.enabled` 设为 `false` 并推送更改。

![密码保护卡片显示“Password not set”状态、警告提示和 Set password 按钮](/images/password-protection/dashboard-pp-notset.webp)

### 3. 在仪表板中设置密码

在仪表板中打开 **Project Settings**，向下滚动到 **Password Protection** 卡片。输入一个强密码短语（至少 8 个字符），然后点击 **Set password**。

卡片会变为 **On** 状态。现在，所有知道密码的人都可以浏览站点；其他人则会看到解锁屏幕。

![密码保护卡片处于 On 状态，显示轮换表单、Revoke all sessions 按钮和停用说明](/images/password-protection/dashboard-pp-on.webp)

<Warning>
  Jamdesk 绝不会存储您的明文密码。密码会在仪表板数据库中使用 scrypt 哈希处理，也不会写入代码仓库或 `docs.json`。因此，如果您忘记密码，Jamdesk 无法通过电子邮件将密码发给您。请改为轮换密码。
</Warning>

### 4. 验证访问限制

在无痕浏览器窗口中打开文档站点（或使用 `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 以供下一个请求使用，即可进入站点。

### 公开例外

整个站点模式有一个例外机制：即使站点其余部分受访问限制，您仍可以让指定页面保持公开。这样，您就能在私有文档旁发布营销落地页或注册表单。

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

**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"] }
        ]
      }
    ]
  }
}
```

如果 frontmatter 和导航无法满足需求，可以使用 `auth.password.public[]` 下的**显式 glob 模式**，以涵盖顶层落地页、动态生成的路由，或您不想重写的整个子目录。

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

Glob 支持 `*`（一个路径段）和 `**`（任意深度）。验证时会拒绝单独的 `/`：如果 Jamdesk 接受它，一处拼写错误就可能在无提示的情况下解锁整个站点。每次构建后，仪表板卡片都会显示解析后的允许列表，方便您检查构建实际识别到的内容。

## 只保护少数页面

指定页面模式采用相反的流程：默认情况下所有内容都是公开的，您可以选择将个别页面设为受保护页面。

### 1. 将页面标记为私有

在页面的 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`。设置 `auth.password.private[]` 而不设置 `enabled`，即可自动启用指定页面模式。

### 2. 提交并推送

推送更改。下次构建时，系统会检测私有页面、启用指定页面模式的保护，并提示您在仪表板中设置密码，流程与整个站点模式完全相同。

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

### 3. 设置密码

打开 **Project Settings**，找到 **Password Protection** 卡片并设置密码短语。卡片标题现在会显示 **On** 和 **Specific pages**，而不是 **Whole site**；卡片还会列出构建解析出的私有页面，方便您快速检查。

![密码保护卡片处于指定页面模式，列出三个私有路径并显示更新后的停用说明](/images/password-protection/dashboard-pp-specific.webp)

### 4. 验证访问限制

像平常一样浏览文档站点。公开页面应照常加载；私有页面则应将您重定向到解锁屏幕。输入密码后，该设备会保持登录状态 30 天，期间您可以阅读任何私有页面，无需再次输入密码。

## 访客看到的内容

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

![ACME 解锁屏幕，包含站点名称、锁图标、密码字段和下方的提示文字](/images/password-protection/unlock-screen.webp)

卡片会使用您在 `docs.json` 中设置的站点徽标和主色。密码字段带有显示密码的切换按钮，并会自动获得焦点。

输入错误密码时，页面会显示同一张卡片，其中包含错误消息、新的输入框，以及每次尝试之间的短暂延迟。错误密码和未输入密码的请求都会显示同一屏幕，因此页面不会透露“密码错误”还是“尚未输入密码”。

![解锁失败后的屏幕，红色显示“Incorrect password. Please try again.”](/images/password-protection/unlock-screen-error.webp)

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

## 轮换和撤销会话

共享密码迟早需要更换，例如密码已经被多人转发，或有人离开团队时。

打开 **Password Protection** 卡片，在 **Rotate password** 字段中输入新的密码短语，然后点击 **Save new password**。下次发起请求时，持有旧密码的用户会被拒绝访问；持有新密码的用户则可以进入。轮换会立即生效，无需重新构建。

如果您只想强制退出所有活动会话，而不更改密码短语（例如某人的笔记本电脑丢失），请点击 **Revoke all sessions**。系统会递增服务器端的版本计数器，使递增前签发的所有 Cookie 失效。访客重新输入当前密码后即可再次进入。

## 禁用保护

保护设置由 `docs.json` 控制，因此要关闭保护，需要编辑该文件并推送更改。

- **整个站点：** 删除 `auth.password.enabled`（或将其设为 `false`）。
- **指定页面：** 删除所有 `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 或泄露计时信息。达到任一限制时，系统会返回 `429 Too Many Requests` 和 `Retry-After` 标头。

**存储。** 您的密码会使用 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`，因此搜索爬虫无法为访问限制后面的内容编制索引。受保护站点内的公开页面仍可正常编入索引。

## 下一步是什么？

<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（Enterprise）" 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>
