物联网系列-EMQXHTTP认证

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战」。

往前文章:

物联网系列 - 初识MQTT

物联网系列 - MQTT协议原理与数据包结构

物联网系列 - EMQ X简介与安装

物联网系列 - EMQ X Dashboard

物联网系列 - EMQ X 认证介绍

物联网系列 - EMQ X Username 认证

物联网系列 - EMQ X Client ID 认证

HTTP 认证使用外部自建 HTTP 应用认证数据源,根据 HTTP API 返回的数据判定认证结果,能够实现复杂的认证鉴权逻辑。启用该功能需要将 emqx_auth_http 插件启用,并且修改该插件的配置文件,在里面指定HTTP认证接口的url。 emqx_auth_http 插件同时还包含了ACL的功能,我们暂时还用不上,通过注释将其禁用。

1:在Dashboard中中开启 emqx_auth_http 插件,同时为了避免误判我们可以停止通过username,clientID 进行认证的插件 emqx_auth_clientid , emqx_auth_username

1. 认证原理

EMQ X 在设备连接事件中使用当前客户端相关信息作为参数,向用户自定义的认证服务发起请求查询权限,
通过返回的 HTTP 响应状态码 (HTTP statusCode) 来处理认证请求。

  • 认证失败:API 返回 4xx 状态码
  • 认证成功:API 返回 200 状态码
  • 忽略认证:API 返回 200 状态码且消息体 ignore

2. HTTP 请求信息

HTTP API 基础请求信息,配置证书、请求头与重试规则。

# etc/plugins/emqx_auth_http.conf
## 启用 HTTPS 所需证书信息
## auth.http.ssl.cacertfile = etc/certs/ca.pem
## auth.http.ssl.certfile = etc/certs/client-cert.pem
## auth.http.ssl.keyfile = etc/certs/client-key.pem
## 请求头设置
## auth.http.header.Accept = */*
## 重试设置
auth.http.request.retry_times = 3
auth.http.request.retry_interval = 1s
auth.http.request.retry_backoff = 2.0
复制代码

加盐规则与哈希方法

HTTP 在请求中传递明文密码,加盐规则与哈希方法取决于 HTTP 应用。

3. 认证请求

进行身份认证时,EMQ X 将使用当前客户端信息填充并发起用户配置的认证查询请求,查询出该客户端在
HTTP 服务器端的认证数据。

打开etc/plugins/emqx_auth_http.conf配置文件,通过修改如下内容:修改完成后需要重启EMQX服务

# etc/plugins/emqx_auth_http.conf
## 请求地址 填写 http 验证服务的地址
auth.http.auth_req = http://192.168.10.20:8991/mqtt/auth
## HTTP 请求方法
## Value: post | get | put
auth.http.auth_req.method = post
## 请求参数
auth.http.auth_req.params = clientid=%c,username=%u,password=%P
复制代码

HTTP 请求方法为 GET 时,请求参数将以 URL 查询字符串的形式传递;POST、PUT 请求则将请求参数以普通
表单形式提交(content-type 为 x-www-form-urlencoded)。

你可以在认证请求中使用以下占位符,请求时 EMQ X 将自动填充为客户端信息:

  • %u:用户名
  • %c:Client ID
  • %a:客户端 IP 地址
  • %r:客户端接入协议
  • %P:明文密码
  • %p:客户端端口
  • %C:TLS 证书公用名(证书的域名或子域名),仅当 TLS 连接时有效
  • %d:TLS 证书 subject,仅当 TLS 连接时有效

推荐使用 POST 与 PUT 方法,使用 GET 方法时明文密码可能会随 URL 被记录到传输过程中的服务器日志中。

4. 认证服务开发

创建基于springboot的应用程序: emq-demo

pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>emq-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>emq-demo</name>
    <description>emq demo演示</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
复制代码

创建application.yml配置文件并配置

server:
  port: 8991
spring:
  application:
    name: emq-demo
复制代码

创建Controller

package com.example.emqdemo.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/mqtt")
@Slf4j
public class AuthController {

    private Map<String,String> users;

    @PostConstruct
    public void init(){
        users = new HashMap<>();
        //实际的密码应该是密文,mqtt的http认证组件传输过来的密码是明文,
        // 我们需要自己进行加密验证
        users.put("user","123456");
        users.put("emq-client2","123456");
        users.put("emq-client3","123456");
    }

    @PostMapping("auth")
    public ResponseEntity<?> auth(@RequestParam("clientid") String clientid,
                       @RequestParam("username") String username,
                       @RequestParam("password") String password) {
        log.info("emqx认证组件调用自定义的认证服务开始认证,clientid={},username={},password= {}",clientid,username,password);
        //在此处可以进行复杂也的认证逻辑,但是我们为了演示方便做一个固定操作
        String userPassword = users.get(username);
        if (StringUtils.isEmpty(userPassword) || !userPassword.equals(password)) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(HttpStatus.UNAUTHORIZED);
        }
        return ResponseEntity.ok(HttpStatus.OK);
    }
}
复制代码

MQTTX客户端验证

http认证.jpg

这个地方的Client-ID随便输入,因为在验证的代码里没有对该字段做校验,之后点连接,发现会连接成功,然
后可以去自定义的认证服务中查看控制台输出,证明基于外部的http验证接口生效了。在实际项目开发过程中,
HTTP接口校验的代码不会这么简单,账号和密码之类的数据肯定会存在后端数据库中,代码会通过传入的数据和
数据库中的数据做校验,如果成功才会校验成功,否则校验失败。

成功后可以在 emqx 控制台看到连接的客户端信息

http认证.jpg

当然EMQ X除了支持我们之前讲过的几种认证方式外,还支持其他的认证方式,比如:MySQL认证、
PostgreSQL认证、Redis认证、MongoDB认证,对于其他这些认证方式只需要开启对应的EMQ X插件并且配置对
应的配置文件,将对应的数据保存到相应的数据源即可。