> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arupa.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 访问控制

> 了解 Arupa 如何验证用户身份并评估访问策略。

Arupa 分层评估访问权限。内核首先解析请求用户的身份，
然后应用保护主机路径、插件和特定插件资源的策略。
应用于请求的每一层都必须允许该请求。
更具体的策略不会覆盖更广泛的限制，插件也不能使用其自身的资源策略来绕过 `Route.Allow` 或 `Plugin.Allow`。

## 访问层

| 层    | 定义者                            | 保护对象                              |
| ---- | ------------------------------ | --------------------------------- |
| 用户身份 | `Users` 和 `Groups`             | 请求关联的身份和组成员身份                     |
| 主机路由 | `config.toml` 中的 `Route.Allow` | HTTP 请求到达主机或插件处理程序之前的路径           |
| 插件   | `Plugins.<name>` 下的 `Allow`    | 插件拥有的所有 HTTP 路由、静态挂载、命名空间和事件      |
| 插件资源 | 插件的注册数据                        | 一个 HTTP 路由、静态挂载、Socket.IO 命名空间或事件 |

前三层由内核配置。最后一层由插件在注册其资源时声明。

## 用户身份

对于 HTTP 请求，内核首先检查 `arupa-auth` 会话 cookie，
然后检查 `Authorization` 标头。有效的会话会解析为一个用户名。
内核在 `Groups` 中查找该用户名，并创建一个主机验证的身份，其中包含：

* 用户名；
* 包含该用户名的组；以及
* `Authenticated` 状态。

无效或缺失的会话会产生未认证的身份。内核不会为未认证的请求向插件发送用户对象。

Socket.IO 在握手过程中使用会话 cookie 或 `Authorization` 标头执行相同的身份验证解析。

## 策略语义

所有访问检查都使用相同的策略模型：

```text theme={null}
AccessPolicy {
    RequireAuth: true 或 false
    Groups: 组名列表
}

```

结果按如下方式确定：

* 空策略为公开策略，允许未经身份验证的用户访问。
* `RequireAuth = true` 且未指定任何组名，允许任何已验证的用户访问。
* 非空的 `Groups` 列表要求用户进行身份验证，并且至少属于一个列出的组。
* 已验证但未属于任何列出的组的用户将被禁止访问。

组列表是一个“或”列表。例如，`Groups = ["root", "staff"]`
允许 `root` 或 `staff` 组的用户访问；用户无需同时属于这两个组。

两种拒绝状态不同：

* 未经身份验证的请求是身份验证失败，返回 `401 Unauthorized`。
* 已验证但未满足组要求的用户是授权失败，返回 `403 Forbidden`。

## 主机路由访问

`Route.Allow` 在请求到达主机处理程序或插件处理程序之前保护 HTTP 路径：

```toml theme={null}
[Route.Allow]
"/api/plugins/launch" = ["root"]
"/api/admin/" = ["root"]
```

映射值是一个组列表。当已验证的用户属于列表中的至少一个组时，请求才被允许。

`Route.Allow` 映射使用内核的共享路径模式实现。普通路径是精确匹配，而以 `/` 结尾的模式匹配该路径的子树。`/*` 不是有效的配置；它不支持通配符。
当多个 `Route.Allow` 模式匹配时，匹配长度最长的模式生效。
内核不会合并来自多个匹配模式的组。请参阅
[路由](./route) 以了解完整的路径模式规则、

根路径行为和验证详情。
如果没有匹配的规则，`Route.Allow` 不会限制请求。
如果匹配的规则的组列表为空，则其策略实际上也是公开的，
因为空的访问策略允许所有人访问；空列表并不意味着“拒绝所有请求”。
外部身份验证中间件会对每个 HTTP 请求评估 `Route.Allow`。
当它拒绝请求时，会返回标准的 JSON `401` 或 `403` 响应。
它不使用已配置的 `Pages` 重定向。
请参阅 [页面](./config-pages)以了解插件保护资源使用的浏览器重定向。

## 插件级访问权限

`[Plugins.<name>]` 下的 `Allow` 策略适用于整个插件：

```toml theme={null}
[Plugins.hello]
Allow = ["staff"]
```

此策略会在请求或事件到达插件拥有的任何资源之前进行检查。

它适用于：

* 插件的 HTTP 路由；
* 插件的静态文件挂载；
* 连接到插件的 Socket.IO 命名空间；以及
* 在这些命名空间中接收到的事件。

空的 `Allow` 列表表示插件在此层保持开放状态。
非空列表要求用户至少属于列出的组之一。

`[Plugins.default]` 提供插件的基本配置。
`[Plugins.<name>]` 中的非空 `Allow` 列表会替换该插件的默认组列表。

如果每个插件的列表被省略或为空，则当前实现将保留默认列表。
当内核重新加载其配置时，插件策略也会刷新，
因此加载插件会使用更新后的组列表来处理后续的请求和事件。

## 插件资源访问

插件在注册资源时可以添加其他策略。
这些策略并非写在 `config.toml` 文件中；它们是插件注册协议的一部分。

HTTP 路由和静态挂载点各自拥有自己的 `Access` 策略。
内核首先检查插件级别的策略，然后检查资源级别的策略：

```text theme={null}
plugin Allow -> HTTP 路由 Access
plugin Allow -> static mount Access
```

对于 Socket.IO，检查分两个级别进行：

```text theme={null}
connect: plugin Allow -> namespace Access
event: plugin Allow -> namespace Access -> event Access
```

事件策略是可选的。如果命名空间没有为某个事件定义策略，
则命名空间策略仍然适用，但不会进行额外的事件检查。
插件未声明的事件将被忽略。
由于每个检查都必须通过，因此有效权限是适用层级的交集。
例如，用户必须同时属于路由的 `Plugin.Allow` 中的 `staff` 组和 `Access.Groups` 中的 `operators` 组才能访问该路由。

## 示例

使用以下配置：

```toml theme={null}
[Users]
admin = "..."

[Groups]
root = ["admin"]
staff = ["alice"]

[Route.Allow]
"/api/plugins" = ["root"]

[Plugins.hello]
Allow = ["staff"]
```

`admin` 用户可以访问 `/api/plugins` 主机路由规则，因为他们属于 `root` 组。`alice` 已通过身份验证，但访问该路径时会收到 `403 Forbidden` 错误，因为他们不属于 `root` 组。未经身份验证的请求会收到 `401 Unauthorized` 错误。
同一个 `admin` 用户无法访问任何受 `hello` 插件的 `Allow = ["staff"]` 保护的资源。
因为 `admin` 不在 `staff` 组中。当策略指定了用户组时，仅通过身份验证是不够的。
