基于 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 属性,将授权请求发送到不同的端点:
{
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 配置
在 EMQX Dashboard 页面,点击左侧导航栏的访问控制 -> 授权,进入授权页面。
在授权页面,点击创建,选择数据源为
HTTP Server,点击下一步,进入配置参数步骤:
根据如下说明完成相关配置:
请求方式:选择 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。在动态模式下,该设置不生效。 - 请求超时(可选):填入请求超时等待时长,可选单位:小时、分钟、秒、毫秒。
- 连接池大小(可选):在
最后点击创建完成相关配置。
配置 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 服务不可用。
响应示例:
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 请求配置的示例:
{
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 请求配置的示例:
{
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 同级:
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:
{
...
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 主题发布消息。
GET请求配置如下:
{
method = get
url = "http://127.0.0.1:8080/authz/${clientid}"
body {
username = "${username}"
topic = "${topic}"
action = "${action}"
}
}最终的 HTTP 请求会是下面这样:
GET /authz/emqx_c?username=emqx_u&topic=t%2F1&action=publish HTTP/1.1
... Headers ...POSTJSON 格式的请求配置如下:
{
method = post
url = "http://127.0.0.1:8080/authz/${clientid}"
body {
username = "${username}"
topic = "${topic}"
action = "${action}"
}
headers {
"content-type": "application/json"
}
}最终的 HTTP 请求会是下面这样:
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:
{
"accept" = "application/json"
"cache-control" = "no-cache"
"connection" = "keep-alive"
"keep-alive" = "timeout=30, max=1000"
}GET 请求的 Headers 不能包含 content-type Header。
对于 POST 请求有以下默认 Headers:
{
"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 时,该配置不生效。
请求配置
以下都是可选字段,
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 选项。