Skip to content

基于 HTTP 应用进行授权

TIP

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

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

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

通过 Dashboard 配置

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

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

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

    • 请求方式:选择 HTTP 请求方式,可选值:getpost

      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_typeclient_idclient_secret 和可选的 scope。Token Endpoint 必须返回状态码 200,JSON 响应体中必须包含 access_token,还可以包含 token_typeexpires_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}: 客户端使用的协议名称。例如 MQTTCoAP 等。
  • ${mountpoint}: 网关监听器的挂载点(主题前缀)。
  • ${action}: 当前执行的动作请求,例如 publishsubscribe
  • ${topic}: 当前请求想要发布或订阅的主题(或主题过滤器)。
  • ${qos}: 当前请求想要发布或订阅的消息 QoS。
  • ${retain}: 当前请求想要发布的消息是否为保留消息。
  • ${zone}: 客户端在运行时的 Zone。Zone 是对客户端的一种逻辑分类,例如区域或环境,可以根据客户端的配置动态应用。

响应格式

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

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

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

TIP

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

配置项

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

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 客户端凭证认证。该配置块与 methodurlbodyheaders 同级:

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_ut/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 选项。