# 全局命名空间设置

在 EMQX 6.1 中，除了可以对单个命名空间实例进行配置外，还提供了一组全局命名空间设置，用于控制命名空间的识别方式、隔离行为以及与主题和授权相关的处理逻辑。

这些设置作用于整个集群，并对所有命名空间和客户端连接生效，通常需要在启用和使用命名空间功能之前进行配置。

全局命名空间设置可通过 Dashboard 进行管理，路径为：**管理** -> **命名空间** -> **设置**。

::: tip 提示

为保持向后兼容性，EMQX 6.1 中的大多数全局命名空间设置（如客户端 ID 隔离、将命名空间作为主题挂载点、授权检查包含挂载点）默认均为关闭状态。  

如需启用相应的隔离能力，请在**命名空间相关配置**中显式开启相关配置。

:::

![全局命名空间设置，包括禁止使用的命名空间名称](./assets/namespace_global_settings.png)

## 仅允许显示创建的命名空间

该配置用于控制客户端是否只能连接到已显式创建的命名空间，在配置文件中对应 `multi_tenancy.allow_only_managed_namespaces`。

当启用该配置时，EMQX 会在客户端连接阶段对命名空间进行校验，并据此决定是否允许连接。

- **启用**：
  - 如果客户端所属的命名空间不是通过 Dashboard 或 REST API 显式创建的，该客户端将被拒绝连接。
  - 如果客户端的命名空间无法解析（例如未配置命名空间来源，或命名空间来源规则未生成有效值），该客户端也将被拒绝连接。
- **关闭**：
  - 允许客户端连接到未显式创建的命名空间。
  - 在满足命名空间来源规则的前提下，EMQX 可根据需要自动创建命名空间。

::: tip 提示

启用该配置前，请确保已正确配置**命名空间来源**，并且所有合法客户端都能够解析出已显式创建的命名空间。否则，客户端可能因无法解析命名空间或命名空间尚未显式创建而被拒绝连接。

当**命名空间解析时机**设置为**认证后**时，认证前的命名空间校验将被跳过，对显式创建命名空间的检查将在认证完成后执行。

:::

## 默认最大会话数

该配置用于为新创建的命名空间设置默认的最大会话数上限。

- **启用**：新创建的命名空间将自动继承该最大会话数限制。
- **关闭**：新创建的命名空间默认不限制最大会话数（`infinity`）。

该配置仅对新创建的命名空间生效，不会影响已经存在的命名空间。已存在命名空间的最大会话数需在对应命名空间的配置中单独修改。

## 禁止使用的命名空间名称

从 EMQX 6.3.0 开始，`multi_tenancy.deny_namespaces` 用于指定不能用作命名空间标识的名称。该限制适用于 Dashboard 用户角色、API 密钥、通过管理 API 创建和批量导入命名空间，以及通过 `client_attrs.tns` 为客户端分配命名空间。

默认列表为 `["global", "undefined", "null", "none"]`。这些名称在日志和 Dashboard 输出中容易与内部标识混淆。

在 Dashboard 中编辑该列表：

1. 进入**管理** -> **命名空间** -> **设置**。
2. 在**禁止使用的命名空间名称**中，按需添加或移除名称。清空所有条目可关闭名称限制。
3. 点击**确定**应用更改。

您也可以在 `etc/base.hocon` 中配置该列表。以下示例使用默认值：

```hocon
multi_tenancy.deny_namespaces = ["global", "undefined", "null", "none"]
```

自定义列表会替换默认列表。如果仍需禁止某些默认名称，请将其保留在列表中。设置 `multi_tenancy.deny_namespaces = []` 可关闭名称限制。配置文件的优先级请参见[配置覆盖规则](../configuration/configuration.md#配置覆盖规则)。

如果客户端解析出的命名空间在该列表中，EMQX 将拒绝连接并返回 `not_authorized`，即使已关闭**仅允许显式创建的命名空间**也是如此。当 `multi_tenancy.allow_only_managed_namespaces = false` 时，该名称限制不会阻止未分配命名空间的客户端连接。

::: warning 重要提示

默认列表会禁止使用 EMQX 6.3.0 之前允许的名称。EMQX 不会自动迁移使用这些名称的命名空间。升级前，请更换受影响的命名空间名称，或调整 `multi_tenancy.deny_namespaces` 以允许使用这些名称。

:::

## 命名空间解析时机

该设置控制 EMQX 在连接生命周期的哪个阶段解析客户端的命名空间标识。

EMQX 支持两种模式，可在 Dashboard 中通过**命名空间解析时机**单选按钮进行选择：

- **认证前**（默认）：在认证链执行前，使用当时可用的连接元数据（如 `username`、`clientid`、`cert_common_name` 等）对命名空间表达式进行求值。在配置文件中，对应通过 `mqtt.client_attrs_init` 配置 `tns` 属性。
- **认证后**：在完整的认证链执行完成后，对命名空间表达式进行求值。除标准连接元数据外，还可访问 `client_attrs.*` 值，包括认证后端返回的属性（例如，HTTP 认证后端返回的 `tag` 字段）。在配置文件中，对应配置 `multi_tenancy.post_auth_tns_expression`。

::: tip

若配置了**认证后**模式，EMQX 将使用认证后表达式分配命名空间。当该表达式求值为空或出错时，不会回退到认证前的 `tns` 值。详情请参见[认证后表达式为空或求值出错](#认证后表达式为空或求值出错)。

:::

### 与"仅允许显式创建的命名空间"的交互

选择**认证后**模式时，认证前的命名空间检查会被完全跳过，即使已启用**仅允许显式创建的命名空间**也是如此。所有校验（命名空间是否已存在、配额检查）均推迟到认证完成、最终命名空间值确定后再执行。

## 命名空间来源

该设置定义 EMQX 用于派生客户端命名空间标识（`client_attrs.tns`）的 Variform 表达式。

表达式的求值时机由**命名空间解析时机**设置决定：

- **认证前**模式下，仅可使用标准连接元数据：`username`、`clientid`、`cert_common_name` 及其他预认证属性。
- **认证后**模式下，还可使用 `client_attrs.*`，包括认证结果中合并进来的属性。

::: tip

命名空间来源规则使用 Variform 表达式进行定义，有关 Variform 表达式的语法和可用函数，请参考 [Variform 表达式](../configuration/configuration.md#variform-表达式)。

:::

该配置是以下功能的前提条件：

- 自动创建命名空间
- 基于命名空间的主题隔离
- 基于命名空间的客户端 ID 隔离
- 命名空间级会话限制与速率限制

如果未配置命名空间来源，则客户端不会被分配到任何命名空间，相关隔离和控制功能也不会生效。

### 示例

#### 认证前

从用户名中提取命名空间：

```text
nth(1, tokens(username, '-'))
```

在该配置下，使用用户名 `tenantA-user1` 连接的客户端，会在认证执行前将 `tenantA` 赋值为其命名空间标识。

#### 认证后

使用 HTTP 认证后端返回的 `tag` 属性：

```text
client_attrs.tag
```

带回退（当认证后端未返回 `tag` 时，回退到 `username`）：

```text
coalesce(client_attrs.tag, username)
```

在该配置下，EMQX 等待认证链执行完毕后，从合并后的 `client_attrs` 中读取 `tag` 值，并将其赋值为命名空间标识。

### 认证后表达式为空或求值出错

配置 `multi_tenancy.post_auth_tns_expression` 后，如果表达式求值为空字符串或出错，EMQX 将按以下规则处理连接。求值出错时还会记录一条警告日志。

1. 如果认证前的 `client_attrs.tns` 值在 `multi_tenancy.deny_namespaces` 列表中，EMQX 将拒绝连接并返回 `not_authorized`。
2. 否则，EMQX 将客户端视为未分配命名空间：
   - 当 `multi_tenancy.allow_only_managed_namespaces = true` 时，EMQX 拒绝连接并返回 `not_authorized`。
   - 当 `multi_tenancy.allow_only_managed_namespaces = false` 时，EMQX 清除认证前的 `tns` 值（如有），允许客户端以无命名空间状态连接。

## 客户端 ID 隔离

客户端 ID 隔离用于解决多租户场景下不同命名空间使用相同客户端 ID 导致冲突的问题。

EMQX 在全局范围内使用有效客户端 ID 标识会话，而不是使用命名空间和客户端 ID 的组合。因此，客户端 ID 隔离通过生成全局唯一的有效客户端 ID 来避免冲突，通常会在原始客户端 ID 前添加命名空间前缀。客户端仍发送原始客户端 ID，EMQX 在内部将覆盖后的 ID 用作有效客户端 ID。

### 选择客户端 ID 覆盖机制

请根据命名空间信息的来源以及有效客户端 ID 是否必须包含命名空间，选择覆盖机制：

- 如果命名空间在认证前生成，请配置 `mqtt.clientid_override`。EMQX 在 `mqtt.client_attrs_init` 执行完成后、认证开始前对该表达式求值，因此表达式可以使用由 `mqtt.client_attrs_init` 初始化的属性，包括 `client_attrs.tns`。
- 如果命名空间来自认证结果，并且有效客户端 ID 必须包含该命名空间，请配置[认证后端返回 `clientid_override`](../access-control/authn/authn.md#通过认证结果覆盖客户端-id)。返回值必须包含完整的新客户端 ID。`mqtt.clientid_override` 表达式无法使用认证后端返回的属性，也无法使用 `multi_tenancy.post_auth_tns_expression` 生成的命名空间。
- 如果 `multi_tenancy.post_auth_tns_expression` 设置命名空间，但有效客户端 ID 不需要包含该命名空间，则只有在客户端已使用全局唯一客户端 ID 时，才无需配置客户端 ID 覆盖。

一个连接只应使用一种客户端 ID 覆盖机制。如果同时配置两种机制，认证结果覆盖会在稍后执行，并替换 `mqtt.clientid_override` 生成的客户端 ID。无论使用哪种机制，都必须确保最终生成的客户端 ID 全局唯一。

### EMQX 应用客户端 ID 覆盖的顺序

EMQX 按以下顺序确定有效客户端 ID：

1. 通过 `mqtt.client_attrs_init` 初始化客户端属性。
2. 在认证前对 `mqtt.clientid_override` 求值。
3. 认证客户端，并应用认证成功结果中返回的非空 `clientid_override`。
4. 对 `multi_tenancy.post_auth_tns_expression` 求值。
5. 使用有效客户端 ID 打开客户端会话。

EMQX 不会在认证后再次对 `mqtt.clientid_override` 求值，也不会自动将认证后获取的命名空间添加到客户端 ID。如果认证成功结果中未包含 `clientid_override` 或其值为空，EMQX 将继续使用此前确定的客户端 ID。

### 配置认证前客户端 ID 隔离

在 Dashboard 中启用客户端 ID 隔离时，EMQX 会配置 `mqtt.clientid_override` 并自动填入一个推荐表达式：

```
concat([client_attrs.tns, '-', clientid])
```

::: warning 重要提示

从 EMQX 6.3.0 开始，如果 `mqtt.clientid_override` 表达式求值出错或生成空字符串，EMQX 会记录错误日志并拒绝连接。MQTT 5.0 客户端会收到 CONNACK 原因码 `0x85`（`Client Identifier not valid`），MQTT 3.1 和 3.1.1 客户端会收到返回码 `2`。EMQX 不会回退到客户端提供的 Client ID。

升级前，请确认每个连接的客户端都能将已配置的表达式求值为非空字符串。如果某个客户端无法生成有效结果，请修正表达式或该客户端必需的数据。

:::

在上述配置下：

- 不同命名空间中的客户端即使使用相同的 Client ID，也不会发生冲突。
- 内部实际使用的客户端 ID 将包含命名空间前缀。

该表达式仅作为认证前生成命名空间时的示例。可以根据业务需要调整表达式，但必须确保最终生成的客户端 ID 在全局范围内保持唯一。

### 实际效果示例

假设已配置命名空间来源，并从用户名中提取命名空间：

```text
nth(1, tokens(username, '-'))
```

并启用了客户端 ID 隔离，使用默认表达式：

```text
concat([client_attrs.tns, '-', clientid])
```

#### 客户端连接信息

| 客户端 | 用户名        | Client ID |
| ------ | ------------- | --------- |
| A      | tenantA-user1 | client1   |
| B      | tenantB-user2 | client1   |

#### EMQX 内部实际使用的客户端 ID

| 命名空间 | 原 Client ID | 实际 Client ID  |
| -------- | ------------ | --------------- |
| tenantA  | client1      | tenantA-client1 |
| tenantB  | client1      | tenantB-client1 |

## 将命名空间作为挂载点

启用该配置后，EMQX 会在成功识别命名空间的前提下，将客户端所属的命名空间作为主题挂载点（mountpoint），用于实现命名空间级别的主题隔离。

如果监听器已单独配置了 `mountpoint`，则会忽略该设置，以监听器上的 `mountpoint` 配置为准。

### 行为说明

启用**将命名空间作为挂载点**后，EMQX 会通过以下方式对主题进行隔离处理：

- 在客户端执行 PUBLISH、SUBSCRIBE、UNSUBSCRIBE 以及遗嘱消息时：
  - EMQX 会在 Broker 内部自动为主题添加命名空间前缀 `{namespace}/`。
- 在消息投递给客户端时：
  - EMQX 会自动移除该命名空间前缀。
- 对客户端而言：
  - 发布和订阅的主题名称始终保持不变。
  - 客户端无需感知命名空间前缀的存在。

### 示例说明

假设客户端所属命名空间为 `n1`，并已启用**将命名空间作为挂载点**。

#### 客户端侧行为

- 客户端订阅主题：`sensors/#`
- 客户端发布主题：`sensor/data`

#### EMQX 内部处理过程

- Broker 在内部注册订阅主题：`n1/sensors/#`
- Broker 在内部路由消息：`n1/sensors/data`
- 消息最终投递给客户端时：`sensors/data`

可以看到：

- 命名空间前缀仅在 EMQX 内部生效。
- 客户端始终使用原始主题名称。
- 不同命名空间的客户端即使使用相同主题，也不会互相收到消息。

## 授权检查时包含挂载点

该配置用于控制在执行授权（ACL）校验时，目标主题和主题过滤器是否在匹配 ACL 规则或授权器之前，自动加上主题挂载点前缀。

该挂载点前缀通常来自命名空间（当启用“将命名空间作为挂载点”时），其格式为：`{namespace}/`。

### 行为说明

启用**授权检查时包含挂载点**后，EMQX 的授权校验流程将发生如下变化：

- 在执行 ACL 规则或授权器匹配之前，EMQX 会先为目标主题或主题过滤器添加主题挂载点前缀。
- 随后，使用带有挂载点前缀的主题进行授权校验。

该行为适用于以下操作场景：

- PUBLISH
- SUBSCRIBE
- UNSUBSCRIBE
- 遗嘱消息

### 示例说明

假设已启用以下配置：

- **将命名空间作为挂载点**
- **授权检查时包含挂载点**
- 客户端所属命名空间为 `n1`

#### 客户端侧行为

客户端尝试订阅主题：`sensors/#`。

#### 授权检查时的实际匹配主题

在执行授权校验时，EMQX 使用的主题为：`n1/sensors/#`。因此，对应的 ACL 规则应配置为：`n1/sensors/#`，而不是 `sensors/#`。

### 使用建议

当启用了**将命名空间作为挂载点**用于主题隔离时，建议同时启用本配置。这样可以确保授权校验所使用的主题与 Broker 内部实际生效的主题名称保持一致，避免出现授权结果与实际路由行为不一致的情况。

## 显示自动创建的命名空间（Dashboard 显示行为）

该配置仅影响 Dashboard 中命名空间列表的显示行为，不影响命名空间的创建或实际生效逻辑。

- **启用**：
   Dashboard 仅显示显式创建的命名空间
- **关闭**：
   Dashboard 同时显示显式创建的命名空间和根据命名空间来源规则自动创建的命名空间
