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
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
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
28
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をやり取りしたいと思います。

ではでは