Skip to content

基于 HTTP 应用进行授权 ​

TIP

从 EMQX v5.8.0 开始,HTTP 认证器支持在响应体中包含权限列表 (ACL) 规则用来为客户端预设权限。建议您使用新格式以获得更好的性能。有关详细信息,请参见 HTTP 认证。

EMQX 支持基于 HTTP 应用进行授权。此时,用户需在外部自行搭建一个 HTTP 应用作为数据源,EMQX 将向 HTTP 服务发起请求并根据 HTTP API 返回的数据判定授权结果,从而实现复杂的授权逻辑。

在 4.x 版本中,EMQX 仅会提示 HTTP API 返回的状态码,如 200 、403,内容则会被丢弃。为了向用户提供更多的信息,我们在 EMQX 5.0 版本中增加了对请求内容的返回。

前置准备

熟悉 EMQX 授权基本概念

配置动态主机名解析 ​

默认情况下,HTTP 授权器在创建时解析 url 中的主机名,并使用持久连接池。如果需要为每次授权请求解析主机名,请将 hostname_resolution 设置为 dynamic。

动态主机名解析还允许在 url 的主机部分使用占位符。例如,以下配置根据客户端的 tenant 属性,将授权请求发送到不同的端点:

hocon
{
    type = http
    method = post
    url = "https://${client_attrs.tenant}.auth.example.com/authz"
    hostname_resolution = dynamic
    allowed_hosts = ["*.auth.example.com"]
    pool_size = 8
    headers {
        "Content-Type" = "application/json"
    }
    body {
        username = "${username}"
        topic = "${topic}"
        action = "${action}"
    }
    ssl {
        enable = true
    }
}

配置动态主机名解析时,请注意:

  • hostname_resolution 的可选值为 static 和 dynamic,默认值为 static。也可以为字面量主机名使用 dynamic,使 EMQX 为每次请求重新解析该主机名。
  • 如果 URL 主机包含占位符,必须将 hostname_resolution 设置为 dynamic,并在 allowed_hosts 中至少配置一个条目。
  • allowed_hosts 中的每个条目必须是精确主机名(例如 auth.example.com)或通配模式(例如 *.auth.example.com)。通配模式匹配指定后缀下的主机名,但不匹配该后缀本身。URL 使用字面量主机名时,allowed_hosts 不生效。
  • 在 URL 的网络位置(authority)部分,只有主机可以包含占位符。协议(scheme)必须是 http 或 https,端口必须是字面量整数。不支持用户信息(userinfo)和片段(fragment)。URL 路径和查询参数仍然支持占位符。
  • 如果 EMQX 无法渲染出有效主机名,或者渲染后的主机名与 allowed_hosts 不匹配,EMQX 不会发送 HTTP 请求,授权检查将失败。
  • 在 dynamic 模式下,发往所有渲染后主机的请求共享一个连接池。pool_size 限制连接池可保留以供复用的空闲连接数。将其设置为 0 可禁用连接复用。enable_pipelining 和 max_inactive 在该模式下不生效。
  • 在 dynamic 模式下发送 HTTPS 请求时,EMQX 会将配置的 TLS 选项应用于渲染后的主机。如果没有显式配置服务器名称指示(SNI),EMQX 将根据渲染后的主机名生成 SNI。
  • hostname_resolution 为 dynamic 时不支持 OAuth2。

通过 Dashboard 配置 ​

  1. 在 EMQX Dashboard 页面,点击左侧导航栏的访问控制 -> 授权,进入授权页面。

  2. 在授权页面,点击创建,选择数据源为 HTTP Server,点击下一步,进入配置参数步骤:

    HTTP authorization
  3. 根据如下说明完成相关配置:

    • 请求方式:选择 HTTP 请求方式,可选值:get、post。

      TIP

      推荐使用 POST 方法。使用 GET 方法时,一些敏感信息(如纯文本密码)可能通过 HTTP 服务器日志记录暴露。此外,对于不受信任的环境,请使用 HTTPS。

    • URL:输入 HTTP 应用的 URL。当主机名解析方式设置为 动态 时,主机部分可以使用授权占位符。

    • 主机名解析方式:设置为 静态 时,在创建授权器时解析固定主机名;设置为 动态 时,为每次请求解析主机名。默认选项为 静态。更多信息,参见配置动态主机名解析。

    • 允许的主机:对应配置项 allowed_hosts。URL 主机包含占位符时,配置渲染后的主机名可以匹配的精确主机名或通配模式。

    • 调用条件:输入可选的 Variform 表达式。仅当表达式计算结果为 true 时,EMQX 才调用此授权检查器。有关表达式语法和可用变量,请参见授权检查器调用条件。

    • 请求头(可选):完成 HTTP 请求头的配置。键和值支持使用占位符。

    • OAuth2 客户端凭证:开启后,EMQX 将获取 Access Token,并将其以 Bearer Token 的形式添加到发往外部 HTTP 授权服务的请求中。有关配置详情,参见配置 OAuth2 客户端凭证认证。

    • 启用 TLS:开启后,对外部 HTTP 授权服务的连接启用 TLS。此开关独立于 OAuth2 客户端凭证配置中的启用 TLS开关。

    • 请求体:配置 HTTP 请求体。键和值支持使用占位符。

    • 高级设置:在此部分配置并发连接、连接超时等待时间、最大 HTTP 请求数以及请求超时时间。

      • 连接池大小(可选):在 静态 模式下,指定持久连接池大小,取值不能小于 1;在 动态 模式下,指定可在请求间复用的连接数,设置为 0 可禁用连接复用。默认值:8。
      • 连接超时(可选):填入连接超时等待时长,可选单位:小时、分钟、秒、毫秒。
      • HTTP 管道(可选):正整数,指定无需等待响应可发出的最大 HTTP 请求数。默认值:100。在 动态 模式下,该设置不生效。
      • 请求超时(可选):填入请求超时等待时长,可选单位:小时、分钟、秒、毫秒。
  4. 最后点击创建完成相关配置。

配置 OAuth2 客户端凭证认证 ​

从 EMQX 6.0.4 开始,HTTP 授权器支持 OAuth 2.0 客户端凭证模式(Client Credentials Grant)。启用 OAuth2 后,EMQX 从配置的 Token 端点(Token Endpoint)获取、缓存并自动刷新 Access Token。EMQX 调用外部 HTTP 授权服务时,会通过 Authorization: Bearer <access_token> 请求头携带该 Token,由外部服务验证 EMQX 的身份。

开启 OAuth2 客户端凭证,然后配置以下设置:

Dashboard 配置项说明
Token 端点必填。用于请求 Access Token 的 OAuth2 授权服务器端点。URL 必须使用 HTTP 或 HTTPS,且不能包含用户信息。
客户端 ID必填。请求 Access Token 时使用的 OAuth2 客户端 ID。
客户端密钥必填。请求 Access Token 时使用的 OAuth2 客户端密钥。
授权范围可选。请求 Access Token 时使用的 OAuth2 授权范围。
Token 请求超时向 Token 端点发送 HTTP 请求的超时时间。默认值为 5 秒。
启用 TLS开启后,对 Token 端点启用 TLS。此开关独立于 OAuth2 配置面板外用于外部 HTTP 授权服务的启用 TLS开关。

EMQX 使用 POST 方法向 Token Endpoint 发送 application/x-www-form-urlencoded 请求。请求体包含 grant_type、client_id、client_secret 和可选的 scope。Token Endpoint 必须返回状态码 200,JSON 响应体中必须包含 access_token,还可以包含 token_type 和 expires_in。如果返回 token_type,其值必须为 Bearer;如果返回 expires_in,其值必须为正整数。

重要提示

  • 启用 OAuth2 后,不要为 HTTP 授权器配置 Authorization 请求头。此请求头与 EMQX 自动生成的 Bearer 认证请求头冲突,EMQX 会拒绝该配置。
  • Token Endpoint 必须从请求体的表单字段中接收 Client ID 和 Client Secret。不支持通过 HTTP Basic Authorization 请求头向 Token Endpoint 发送客户端凭证。

请求格式与返回结果 ​

当客户端发起订阅、发布操作时,HTTP Authorizer 会根据配置的请求模板构造并发送请求到外部 Web 服务(授权服务)。用户需要在授权服务中实现授权检查逻辑并按要求返回结果,EMQX 根据响应结果判断是否具备权限。

请求格式 ​

根据授权服务要求而定,可以使用 JSON 格式,支持在 URL 与请求体中使用以下占位符:

  • ${clientid}: 客户端的 ID。
  • ${username}: 客户端登录时用的用户名。
  • ${client_attrs.NAME}:某个客户端属性。NAME 将在运行时根据预定义配置替换为属性名称。有客户端属性的详细信息,请参见 MQTT 客户端属性。
  • ${peerhost}: 客户端的源 IP 地址。
  • ${proto_name}: 客户端使用的协议名称。例如 MQTT,CoAP 等。
  • ${mountpoint}: 网关监听器的挂载点(主题前缀)。
  • ${action}: 当前执行的动作请求,例如 publish, subscribe。
  • ${topic}: 当前请求想要发布或订阅的主题(或主题过滤器)。
  • ${qos}: 当前请求想要发布或订阅的消息 QoS。
  • ${retain}: 当前请求想要发布的消息是否为保留消息。
  • ${zone}: 客户端在运行时的 Zone。Zone 是对客户端的一种逻辑分类,例如区域或环境,可以根据客户端的配置动态应用。

响应格式 ​

授权服务完成检查后,返回符合以下要求的响应:

  • 响应编码格式 content-type 必须是 application/json。
  • 如果返回的 HTTP 状态码为 200,认证结果通过 Body 中的 result 标示,可选值为:
    • allow:允许发布或订阅
    • deny:禁止发布或订阅
    • ignore: 忽略请求,移交下一个认证器以继续执行认证链
  • 如果返回的 HTTP 状态码为 204,认证结果标示为允许发布或订阅。
  • 除了 200 和 204 以外的其他 HTTP 状态码均标示为 ignore,比如 HTTP 服务不可用。

响应示例:

json
HTTP/1.1 200 OK
Headers: Content-Type: application/json
...
Body:
{
    "result": "allow" | "deny" | "ignore" // Default `"ignore"`
}

EMQX 4.x 兼容性说明

在 4.x 中,EMQX 仅用到了 HTTP API 返回的状态码,而内容则被丢弃。例如 200 表示 allow,403 表示 deny。

因为缺乏丰富的表达能力,在 5.0 中对这一机制进行了不兼容的调整。

TIP

推荐使用 POST 方法。 使用 GET 方法时,一些敏感信息可能通过 HTTP 服务器日志记录暴露。 对于不受信任的环境,应使用 HTTPS。

配置项 ​

支持 HTTP POST 和 GET 请求,它们各自都有一些特定的选项。

HTTP 授权必需使用 type=http的配置。

可选配置项 precondition 接受 Variform 表达式。仅当表达式计算结果为 true 时,EMQX 才调用此授权检查器。未配置 precondition 或该配置项为空时,不设置调用条件。有关详细信息,请参见授权检查器调用条件。

使用 POST 请求配置的示例:

hcl
{
    type = http

    method = post
    url = "http://127.0.0.1:8080/authz?clientid=${clientid}"
    body {
        username = "${username}"
        topic = "${topic}"
        action = "${action}"
    }
    headers {
        "Content-Type" = "application/json"
        "X-Request-Source" = "EMQX"
    }
}

使用 GET 请求配置的示例:

hcl
{
    type = http

    method = get
    url = "http://127.0.0.1:8080/authz"
    body {
        clientid = "${clientid}"
        username = "${username}"
        topic = "${topic}"
        action = "${action}"
    }
    headers {
        "X-Request-Source" = "EMQX"
    }
}

OAuth2 客户端凭证配置 ​

从 EMQX 6.0.4 开始,可以在 HTTP 授权器配置对象中添加 oauth2 配置块以启用 OAuth2 客户端凭证认证。该配置块与 method、url、body 和 headers 同级:

hocon
oauth2 {
    enable = true
    grant_type = client_credentials
    token_endpoint = "https://auth.example.com/oauth/token"
    client_id = "emqx-client"
    client_secret = "emqx-client-secret"
    scope = "authorization.check"
    timeout = 5s
    ssl {
        enable = true
    }
}

如果授权服务器不要求 scope,可以省略该配置项。有关请求格式和限制,参见配置 OAuth2 客户端凭证认证。

method ​

该配置为必填字段,用于指定 http 方法,可以是 get 或者 post。

url ​

发送 HTTP 请求的 URL,可以使用如下占位符:

URL 主机包含占位符时,必须将 hostname_resolution 设置为 dynamic,并配置 allowed_hosts。在 URL 的网络位置(authority)部分,只有主机可以包含占位符;协议(scheme)和端口必须使用字面量。

如果 URL 为 https,必须同时启用 ssl:

hcl
{
    ...
    url = "https://127.0.0.1:8080/authz?clientid=${clientid}"
    ssl {
        enable = true
    }
}

body ​

请求模板,对于 post 请求,它以 JSON 形式在请求体中发送。 对于 get 请求,它被编码为 URL 中的查询参数。映射键和值可以包含 占位符.

根据配置项的不同 body 的序列化方式也可能不同。

假设一个 MQTT 客户端使用客户端标识符 emqx_c、用户名 emqx_u 向 t/1 主题发布消息。

  1. GET 请求配置如下:
hcl
{
    method = get
    url = "http://127.0.0.1:8080/authz/${clientid}"
    body {
        username = "${username}"
        topic = "${topic}"
        action = "${action}"
    }
}

最终的 HTTP 请求会是下面这样:

bash
GET /authz/emqx_c?username=emqx_u&topic=t%2F1&action=publish HTTP/1.1
... Headers ...
  1. POST JSON 格式的请求配置如下:
hcl
{
    method = post
    url = "http://127.0.0.1:8080/authz/${clientid}"
    body {
        username = "${username}"
        topic = "${topic}"
        action = "${action}"
    }
    headers {
        "content-type": "application/json"
    }
}

最终的 HTTP 请求会是下面这样:

bash
POST /authz/emqx_c HTTP/1.1
Content-Type: application/json
... Other headers ...

{"username":"emqx_u","topic":"t/1", "action": "publish"}

headers ​

配置 HTTP 授权请求中的 Headers,可选。

对于 GET 请求有以下默认 Headers:

hcl
{
    "accept" = "application/json"
    "cache-control" = "no-cache"
    "connection" = "keep-alive"
    "keep-alive" = "timeout=30, max=1000"
}

GET 请求的 Headers 不能包含 content-type Header。

对于 POST 请求有以下默认 Headers:

hcl
{
    "accept" = "application/json"
    "cache-control" = "no-cache"
    "connection" = "keep-alive"
    "keep-alive" = "timeout=30, max=1000"
    "content-type" = "application/json"
}

content-type Header 的值定义了 POST 请求的 body 编码方式,目前仅支持 application/json。

enable_pipelining ​

正整数,用于指定可以无需等待响应发出的 HTTP 请求的最大数量 HTTP pipelining。可选,默认值为 100,设置为 1 表示关闭 HTTP pipelining 功能,即恢复成常规的同步请求响应模式。

当 hostname_resolution 设置为 dynamic 时,该配置不生效。

请求配置 ​

以下都是可选字段,

hcl
  connect_timeout = 15s # 连接超时
  max_retries = 5 # 最大重试次数
  request_timeout = 30s # 请求超时限制
  retry_interval = 1s # 重试中间间隔

pool_size ​

可选的非负整数配置,默认值为 8。当 hostname_resolution 设置为 static 时,该配置指定持久连接池大小,取值不能小于 1。当 hostname_resolution 设置为 dynamic 时,该配置限制可在请求间复用的连接数,设置为 0 可禁用连接复用。

ssl ​

用于连接到外部 HTTP 服务器的标准 SSL 选项。