MQTTを使いたい。3

また MQTT です。
今回はちょっとでもセキュアに使っていくために、認証を取り入れてみます。

目次

想定環境

今回は以下の構成で動かすことを考えます。

  • 認証機能付きの Broker
  • 特定トピック A,B のみ送信可能な Publisher
  • 特定トピック A のみ受信可能な Subscriber
  • 特定トピック B のみ受信可能な Subscriber

サンプルコード

認証機能付きの Broker

mqtt サーバー処理 outh_mqtt_bro.js と、認証処理 outh_param.js に分離しました。
これまでのものに

  • クライアント接続時に処理される
    .authenticat(client, username, password, callback)
  • クライアントが Publish したときに処理される
    .authorizePublish(client, topic, payload, callback)
  • クライアントが Subscribe したときに処理される
    .authorizeSubscribe(client, topic, callback)

の 3 件を追加することで、認証処理を追加します。

outh_mqtt_bro.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const mosca = require("mosca");

//認証パラメータ・認証処理
const param = require("./outhparam");

//mqttサーバー作成
const broker = new mosca.Server({ port: 41998 });

//mqttサーバー接続認証処理
broker.authenticate = (client, username, password, callback) => {
console.log(client.id);
//接続時認証処理
if (param.connectauth(client, username, password)) {
//OK
console.log("authenticate-OK");
callback(null, true);
} else {
//NG
console.log("authenticate-NG");
callback(null, false);
}
};

//pubulish認証処理
broker.authorizePublish = (client, topic, payload, callback) => {
if (param.publishauth(client, topic, payload)) {
//OK
callback(null, true);
} else {
//NG
callback(null, false);
}
};

//subscribe認証処理
broker.authorizeSubscribe = (client, topic, callback) => {
//subscriberとしてのID、topicを確認
if (param.subscribeauth(client, topic)) {
//OK
callback(null, true);
} else {
//NG
callback(null, false);
}
};

broker.on("ready", () => console.log("Server is ready."));
broker.on("clientConnected", (client) =>
console.log("broker.on.connected.", "client:", client.id)
);
broker.on("clientDisconnected", (client) =>
console.log("broker.on.disconnected.", "client:", client.id)
);
broker.on("subscribed", (topic, client) =>
console.log("broker.on.subscribed.", "client:", client.id, "topic:", topic)
);
broker.on("unsubscribed", (topic, client) =>
console.log("broker.on.unsubscribed.", "client:", client.id)
);
broker.on("published", (packet, client) => {
if (/\/new\//.test(packet.topic)) return;
if (/\/disconnect\//.test(packet.topic)) return;
console.log(packet.payload.toString());
});
outh_param.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
module.exports = {
//クライアント認証パラメータ
clients: [
{
id: "pubcliant",
username: "pub_user",
password: "xdorytbseyrinseiyvi",
role: "pub",
topic: [`topicA`, `topicB`],
},
{
id: "subcliantA",
username: "sub_user_A",
password: "4u5093e450694e430bw",
role: "sub",
topic: [`topicA`],
},
{
id: "subcliantB",
username: "sub_user_B",
password: "6nwbisehrnvrityube0",
role: "sub",
topic: [`topicB`],
},
],
//接続時認証処理
connectauth: function (client, username, password) {
for (let i = 0; i < this.clients.length; i++) {
if (
client.id == this.clients[i].id &&
username == this.clients[i].username &&
password == this.clients[i].password
) {
return true;
}
}
return false;
},
//publish時認証処理
publishauth: function (client, topic, payload) {
for (let i = 0; i < this.clients.length; i++) {
if (
client.id == this.clients[i].id &&
this.clients[i].topic.indexOf(topic) > -1 &&
this.clients[i].role == "pub"
) {
return true;
}
}
return false;
},
//subscribe時認証処理
subscribeauth: function (client, topic) {
for (let i = 0; i < this.clients.length; i++) {
if (
client.id == this.clients[i].id &&
this.clients[i].topic.indexOf(topic) > -1 &&
this.clients[i].role == "sub"
) {
return true;
}
}
return false;
},
};
  • .authenticat(client, username, password, callback)
  • .authorizePublish(client, topic, payload, callback)
  • .authorizeSubscribe(client, topic, callback)

は、これまでも使用していた

  • .on('clientConnected', function(cliant){})
  • .on('subscribed', function(topic, client));
  • .on('published', function(packet, client));

これらと異なり、接続前・メッセージの発行前・メッセージの受信要求前に処理される。

特定トピック A,B のみ送信可能な Publisher

outh_param.jsへの記載を参照し、
clientIdusernamepasswordの 3 つのパラメータを設定する。

outh_mqtt_pub.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var mqtt = require("mqtt");

//mqttクライアント作成
var client = mqtt.connect("mqtt://localhost:41998", {
clientId: "pubcliant",
username: "pub_user",
password: "xdorytbseyrinseiyvi",
});

//接続時処理
client.on("connect", function () {
console.log("connect!");

//送信データ初期化
let ob = {};
ob.data = {};
ob.data.a = 0;

//データ送信処理(1秒毎にtopicAもしくはtopicBを送信)
setInterval(() => {
ob.data.a += 1;
str = JSON.stringify(ob);
let topic = ob.data.a % 2 == 0 ? "topicA" : "topicB";
client.publish(topic, `${str}`);
console.log(`${topic}<=${str}`);
}, 1000);
});

特定トピック A のみ受信可能な Subscriber

こちらも同様にouth_param.jsへの記載を参照し、
clientIdusernamepasswordを、
subcliantAの組み合わせで設定する。

outh_mqtt_subA.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var mqtt = require("mqtt");

//mqttクライアント作成
var client = mqtt.connect("mqtt://localhost:41998", {
clientId: "subcliantA",
username: "sub_user_A",
password: "4u5093e450694e430bw",
});

//接続時処理
client.on("connect", function () {
client.subscribe("topicA");
client.subscribe("topicB");
});

//受信時処理
client.on("message", function (topic, message) {
console.log(`${topic}=>${message.toString()}`);
});

特定トピック B のみ受信可能な Subscriber

やはり同様にouth_param.jsへの記載を参照し、
clientIdusernamepasswordの 3 つのパラメータを設定する。
こちらはsubcliantBにする。

outh_mqtt_subB.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var mqtt = require("mqtt");

//mqttクライアント作成
var client = mqtt.connect("mqtt://localhost:41998", {
clientId: "subcliantB",
username: "sub_user_B",
password: "6nwbisehrnvrityube0",
});

//接続時処理
client.on("connect", function () {
client.subscribe("topicA");
client.subscribe("topicB");
});

//受信時処理
client.on("message", function (topic, message) {
console.log(`${topic}=>${message.toString()}`);
});

これらを動かすと以下のようになる。
左から、outh_mqtt_bro.js,mqtt_pub.js,mqtt_subA.js,mqtt_subB.jsの実行画面


mqtt_subA.js,mqtt_subB.jsいづれも topicA,topicB の購読を宣言しているのに、
それぞれ、outh_param.jsで設定した topic のみ購読できていることがわかります。

今回は認証機能付きの mqtt サーバーを作成したわけですが、
今までみたいな mqtt の broker に対して、
どんな topic でも、どんなクライアントでも接続できることは決していいことばかりではないと思います。
今回のような制御でそれらが緩和されるといいなと思います。

今回は拡張性が低い(10 クライアント繋ぐなら 10 クライアントの設定の記述が個別に必要)設定なので、
クライアント名にワイルドカードを使用可能にするなど、
もう少し、考えてみたいところです。

次回は、m5stack と mqtt をやり取りしたいと思います。

ではでは