AWS CognitoとAWS IoTを連携させてみる(パート2)

AWS CognitoとAWS IoTを連携させてみる(パート2):

以前、以下の投稿で、AWS Cognitoで認証したユーザからAWS IoTのMQTTにPublishできるようにしました。

AWS CognitoとAWS IoTを連携させてみる

その時は、AWS Cliを使っていたため、自動化できていませんでした。

今回は、少し発展させて、ユーザがAWS Cognitoで認証してMQTTでPublishするまでを行うWebページを作成してみたいと思います。


AWS CognitoのユーザプールとフェデレーテッドアイデンティティのIDプールの作成

今回は、Node.jsによるサーバと、JavascriptによるWebページを作成することがメインです。

AWS Cognitoによるユーザ認証のためのユーザプールの作成と、AWS IoTと連携するためのつなぎとなるフェデレーテッドアイデンティティのIDプールは、作成済みである前提です。

以下の投稿を参考にして、まずはAWS Cliによる手動で、AWS CognitoとAWS IoTが連携できていることを確認してください。

AWS CognitoとAWS IoTを連携させてみる


目指す形

以下の状態となることを目指します。

  • ログインユーザごとにトピック名を割り当てます。これにより、どのユーザからのPublishなのかを区別できるようにします。
  • ログインユーザしか割り当てられたトピックにPublishできないようにするために、ログインユーザにThingを割り当て、ポリシでトピック名とThingを紐づけます。
以上を実現するために、以下の状態を作ります。

  • AWS Cognitoでのログインユーザの識別は、フェデレーテッドアイデンティティのIdentityIdです。
  • そして、ログインユーザに割り当てるThingのThing名を単純にIdentityIdにします。
  • ログインユーザごとに割り当てるトピック名は、/iot/IdentityId にします。ログインユーザ全員にPublishできるように、/iot/allというトピックも考えておきます。


AWS IoTポリシの作成

先に、AWS IoTに設定するポリシを示しておきます。

XXXXXXXXXXXXの部分は、AWSアカウントIDです。リージョンは、ap-northeast-1としています。

TestIotPolicy
{ 
  "Version": "2012-10-17", 
  "Statement": [ 
    { 
      "Effect": "Allow", 
      "Action": [ 
        "iot:Connect" 
      ], 
      "Resource": [ 
        "*" 
      ] 
    }, 
    { 
      "Effect": "Allow", 
      "Action": [ 
        "iot:Publish" 
      ], 
      "Resource": [ 
        "arn:aws:iot:ap-northeast-1:XXXXXXXXXXXX:topic//iot/${iot:Connection.Thing.ThingName}" 
      ] 
    }, 
    { 
      "Effect": "Allow", 
      "Action": [ 
        "iot:Subscribe" 
      ], 
      "Resource": [ 
        "arn:aws:iot:ap-northeast-1:XXXXXXXXXXXX:topicfilter//iot/all" 
      ] 
    }, 
    { 
      "Effect": "Allow", 
      "Action": [ 
        "iot:Receive" 
      ], 
      "Resource": [ 
        "arn:aws:iot:ap-northeast-1:XXXXXXXXXXXX:topic//iot/all" 
      ] 
    } 
  ] 
} 
4つの指定項目があります。上から順番に説明します。

① MQTTブローカに接続するための指定です。

② 自分の名前のトピックにPublishできるようにするための指定です。

③④ 全ユーザ共通のトピックにSubscribeできるようにするための指定です。

(参考)
https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/thing-policy-variables.html

このポリシを登録します。

AWS管理WebコンソールからAWS Iotを開き、左側のナビゲータから、「安全性」→ 「ポリシ」を選択し、右上の「作成」ボタンを押下します。



image.png


作成は、アドバンスモードでもベーシックモードでもどちらでもよいです。

ポリシの名前は、例えば「TestIotPolicy」とします。

フェデレーテッドアイデンティティで作成した「認証されたロール」にも同じ内容でIAMにポリシ作成して割り当てます。(とりあえず、「AWSIoTFullAccess」でもいいですが。。。)


AWS IoTにユーザ登録するためのサーバの作成

AWS IoTへのユーザ登録要求を受け付けるサーバを立ち上げます。

毎度のRESTfulサーバです。以下を参考にしてください。

SwaggerでRESTful環境を構築する

Swagger定義ファイルの該当部分を示します。

swagger.yaml
paths: 
  /iotregister: 
    post: 
      x-swagger-router-controller: routing 
      operationId: iotregister 
      parameters: 
        - in: body 
          name: IotRegister 
          required: true 
          schema: 
            $ref: '#/definitions/IotRegisterRequest' 
      responses: 
        200: 
          description: Success 
          schema: 
            type: object 
 
definitions: 
  IotRegisterRequest: 
    type: object 
    required: 
    - id_token 
    properties: 
      id_token: 
        type: string 
そして、以下がサーバ実装の本体です。Lambdaを想定しています。

index.js
const aws = require('aws-sdk'); 
aws.config.update({region: 'ap-northeast-1'}); 
 
var cognitoidentity = new aws.CognitoIdentity(); 
var iot = new aws.Iot(); 
 
const Response = require('../../helpers/response'); 
 
exports.handler = (event, context, callback) => { 
    var body = JSON.parse(event.body); 
 
    var params = { 
        IdentityPoolId: 【IDプールのID】, 
        Logins: { 
            'cognito-idp.ap-northeast-1.amazonaws.com/【プールID】' : body.id_token 
        } 
    }; 
 
    cognitoidentity.getId(params, (err, data) =>{ 
        if( err ) 
            return callback(err); 
 
        console.log(data.IdentityId); 
 
        var identityId = data.IdentityId; 
 
        var params = { 
            policyName: 'TestIotPolicy', 
            target: identityId 
        }; 
 
        iot.attachPolicy(params, (err, data) =>{ 
            if( err ) 
                return callback(err); 
 
            console.log(data); 
 
            var params = { 
                thingName: identityId 
            } 
 
            iot.createThing(params, (err, data) =>{ 
                if( err ) 
                    return callback(err); 
 
                console.log(data); 
 
                var params = { 
                    principal: identityId, 
                    thingName: identityId 
                }; 
 
                iot.attachThingPrincipal(params, (err, data) =>{ 
                    if( err ) 
                        return callback(err); 
 
                    return callback( null, new Response({ data: data })); 
                }); 
            }); 
        }); 
    }); 
}; 
毎度のユーティリティです。

response.js
class Response{ 
    constructor(context){ 
        this.statusCode = 200; 
        this.headers = {'Access-Control-Allow-Origin' : '*'}; 
        if( context ) 
            this.set_body(context); 
        else 
            this.body = ""; 
    } 
 
    set_error(error){ 
        this.body = JSON.stringify({"err": error}); 
    } 
 
    set_body(content){ 
        this.body = JSON.stringify(content);         
    } 
 
    get_body(){ 
        return JSON.parse(this.body); 
    } 
} 
 
module.exports = Response; 
一部、環境に合わせて変更が必要です。

【IDプールのID】:AWS CognitoのフェデレーテッドアイデンティティのIDプールのIDです。

【プールID】:AWS CognitoのユーザプールのプールIDです。

このRESTfulサーバに、ユーザがサインインしたときに取得したIDトークンを引数にして渡すと、MQTTにPublishできるように登録をしてくれます。

もう少し具体的に見ていきます。

cognitoidentity.getId

 これで、ユーザ認証したIDトークンから、フェデレーテッドアイデンティティのIdentityIdを取得します。

iot.attachPolicy

 これで、IdentityIdのユーザに、AWS IoTのポリシを割り当てます。

iot.createThing

 これで、IdentityIdという名前でIoT Thingを作成します。

iot.attachThingPrincipal

 これで、IdentityIdという名前のIoT Thingと、フェデレーテッドアイデンティティのIdentityIdが紐づきます。


Webページの作成

ユーザ向けのWebページを作成します。

このWebページでは、以下のことができるようにします。

  • ユーザがAWS Cognitoにログインできるリンクを用意します。
  • ログイン後に、さきほどのRESTful環境に対して、AWS IoTへのユーザ登録要求を行うボタンを用意します。
  • AWS IoTに接続し、トピック/iot/all をSubscribeするボタンを用意します。
  • AWS IoTに対して、トピック/iot/IdentityId でPublishするボタンを用意します。
index.html
<!DOCTYPE html> 
<html lang="ja"> 
<head> 
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;"> 
  <meta name="format-detection" content="telephone=no"> 
  <meta name="msapplication-tap-highlight" content="no"> 
  <meta name="apple-mobile-web-app-capable" content="yes" /> 
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"> 
 
  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> 
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> 
  <!-- Latest compiled and minified CSS --> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> 
  <!-- Optional theme --> 
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> 
  <!-- Latest compiled and minified JavaScript --> 
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> 
 
  <title>AWS IoT テスト</title> 
 
  <script src="./dist/js/aws-iot-sdk-browser-bundle.js"></script> 
  <script src="https://cdn.jsdelivr.net/npm/js-cookie@2/src/js.cookie.min.js"></script> 
  <script src="https://unpkg.com/vue"></script> 
</head> 
<body> 
    <div id="top" class="container"> 
        <h1>AWS IoT テスト</h1> 
        <label>IdentityId:</label> {{identityId}}<br> 
        <label>Connected:</label> {{connected}}<br> 
        <br> 
 
        <a href="https://【ドメイン名】.auth.ap-northeast-1.amazoncognito.com/login?client_id=【アプリクライアントID】&redirect_uri=【このWebページのURL】&response_type=token&scope=openid">サインイン</a><br> 
        <br> 
        <button class="btn btn-primary" v-on:click="mqtt_register()">Mqtt登録</button> 
        <button class="btn btn-primary" v-on:click="mqtt_connect()">Mqtt接続</button> 
        <br> 
        <br> 
        <div class="form-group"> 
            <label>Message</label> 
            <input type="text" class="form-control" v-model="publish_message"> 
            <button class="btn btn-primary" v-on:click="mqtt_publish()">MqttPublish</button> 
        </div> 
        <br> 
        <div class="form-group"> 
            <label>Received Message</label> 
            <div class="panel panel-default" v-for="(message, index) in message_list"> 
                <div class="panel-heading"> 
                    {{message.topic}} 
                </div> 
                <div class="panel-body"> 
                    {{message.payload}} 
                </div> 
            </div> 
        </div> 
    </div> 
 
    <script src="js/start.js"></script> 
</body> 
以下の部分を書き換える必要があります。

【ドメイン名】:AWS Cognitoのユーザプールのアプリ統合で指定したドメイン名です。

【アプリクライアントID】:フェデレーテットアイデンティティの認証プロバイダのCognitoタブで指定したアプリクライアントIDに書き換えてください。

【このWebページのURL】:このHTMLファイルを配置しアクセスするときのURLにします。アプリクライアントIDのコールバックURLにこのURLが指定されている必要があります。

Javascript部分です。

start.js
var aws = require('aws-sdk'); 
aws.config.update({region: 'ap-northeast-1'}); 
var cognitoidentity = new aws.CognitoIdentity(); 
 
var iotDevice = require('aws-iot-device-sdk'); 
var iot = new aws.Iot(); 
 
var id_token = null; 
var device; 
 
var vue_options = { 
    el: "#top", 
    data: { 
        progress_title: '', 
        identityId: '', 
        message_list: [], 
        connected: false, 
        publish_message: '' 
    }, 
    computed: { 
    }, 
    methods: { 
        mqtt_publish: function(){ 
            device.publish('/iot/' + this.identityId, this.publish_message); 
        }, 
        mqtt_register: function(){ 
            var body = { 
                'id_token' : id_token 
            }; 
            do_post("【RESTfulサーバのURL】/iotregister", body) 
            .then(response =>{ 
                console.log(response); 
                alert('登録しました。'); 
            }); 
        }, 
        mqtt_connect: function(){ 
            var params = { 
                IdentityId: this.identityId, 
                Logins: { 
                    'cognito-idp.ap-northeast-1.amazonaws.com/【プールID】' : id_token 
                } 
            }; 
            cognitoidentity.getCredentialsForIdentity(params, (err, data) =>{ 
                if( err ) 
                    return callback(err); 
 
                var credential = data.Credentials; 
 
                device = iotDevice.device({ 
                    region: 'ap-northeast-1', 
                    clientId: this.identityId, 
                    accessKeyId: credential.AccessKeyId, 
                    secretKey: credential.SecretKey, 
                    sessionToken: credential.SessionToken, 
                    protocol: 'wss', 
                    port: 443, 
                    host: '【AWS IoTのエンドポイント】', 
                    maximumReconnectTimeMs: 8000, 
                }); 
 
                device.on('connect', () =>{ 
                    console.log('device connect'); 
                    this.connected = true; 
                }); 
                device.on('close', () =>{ 
                    console.log('device close'); 
                    this.connected = false; 
                }); 
                device.on('reconnect', () =>{ 
                    console.log('device reconnect'); 
                }); 
                device.on('offline', () =>{ 
                    console.log('device offline'); 
                }); 
                device.on('error', (error) =>{ 
                    console.log('device error'); 
                }); 
                device.on('message', (topic, payload) =>{ 
                    console.log('device message'); 
                    console.log('topic ', topic); 
                    console.log('payload ', payload.toString('utf-8')); 
 
                    this.message_list.push({ topic: topic, payload: payload.toString('utf-8')}); 
                }); 
 
                device.subscribe('/iot/all'); 
            }); 
        } 
    }, 
    created: function(){ 
    }, 
    mounted: function(){ 
        proc_load(); 
        history.replaceState(null, null, '.'); 
 
        if( hashs.id_token ){ 
            id_token = hashs.id_token; 
            Cookies.set("id_token", id_token, { expires: 7 }); 
        }else{ 
            id_token = Cookies.get("id_token"); 
        } 
 
        if( id_token ){ 
            var params = { 
                IdentityPoolId: 【IDプールのID】, 
                Logins: { 
                    'cognito-idp.ap-northeast-1.amazonaws.com/【プールID】' : id_token 
                } 
            }; 
 
            cognitoidentity.getId( params, (err, data) =>{ 
                if( err ){ 
                    console.log(err); 
                    return; 
                } 
 
                this.identityId = data.IdentityId; 
            }); 
        } 
    } 
}; 
var vue = new Vue( vue_options ); 
 
var hashs = {}; 
var searchs = {}; 
 
function proc_load() { 
  hashs = parse_url_vars(location.hash); 
  searchs = parse_url_vars(location.search); 
} 
 
function parse_url_vars(param){ 
  if( param.length < 1 ) 
      return {}; 
 
  var hash = param; 
  if( hash.slice(0, 1) == '#' || hash.slice(0, 1) == '?' ) 
      hash = hash.slice(1); 
  var hashs  = hash.split('&'); 
  var vars = {}; 
  for( var i = 0 ; i < hashs.length ; i++ ){ 
      var array = hashs[i].split('='); 
      vars[array[0]] = array[1]; 
  } 
 
  return vars; 
} 
 
function do_post(url, body){ 
    const headers = new Headers( { "Content-Type" : "application/json; charset=utf-8" } ); 
 
    return fetch(url, { 
        method : 'POST', 
        body : JSON.stringify(body), 
        headers: headers 
    }) 
    .then((response) => { 
        return response.json(); 
    }); 
} 
以下を書き換えます。

【RESTfulサーバのURL】:先ほど立ち上げたRESTfulサーバのURLです。

【AWS IoTのエンドポイント】:AWS IoTのエンドポイントです。AWS IoTのWeb管理ページの設定にあります。

【IDプールのID】:同上

【プールID】:同上

それでは、さっそくブラウザで開いてみましょう。



image.png


まずは、サインインのリンクをクリックして、サインインします。

そうするとログイン画面が表示されます。(以下の画面は、AWS Cognitoの設定によって見え方は異なります。)



image.png


ログインに成功すると、IdentityIdが表示されます。



image.png


次に、ユーザをAWS IoTに登録するために、「Mqtt登録」ボタンを押下します。

特に問題がなければ、「登録しました」というダイアログが表示さます。

この作業はユーザごとに1回だけでよいです。



image.png


これで、AWS Iotに接続する準備ができました。

「Mqtt接続」ボタンを押下します。そうすると、Connectedがtrue に表示が変わります。



image.png


この状態で、トピック「/iot/all」をSubscribeしている状態になっています。

AWS IoTコンソールから、Publishしてみます。

発行のところのエディットボックスに/iot/allと入力して、「トピックに発行」ボタンを押下します。



image.png


以下の文字列がブラウザに表示されましたでしょうか?

/iot/all

{ "message": "Hello from AWS IoT console" }



image.png


今度は、ブラウザ側からPublishしてみます。

ログインユーザのIdentityIdがブラウザに表示されていますので、それをAWS IoTコンソールに指定します。

トピックのサブスクリプションのところに /iot/IdentityId という感じで指定します。



image.png




image.png


ブラウザから、messageのところに適当な文字列を入れて、「MqttPublish」ボタンを押下します。



image.png


AWS IoTコンソールにその文字列が表示されれば成功です。



image.png


AWS IoTの開発者ガイドもあるのですが、なかなか理解が難しく、手を動かしてみると、ようやくその意味が分かってきました。

AWS IoTを使いこなすうえでの一助になればと思います。

以上です。

コメント

このブログの人気の投稿

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2021-04-30 23:37:32 RSSフィード2021-04-30 23:00 分まとめ(42件)

投稿時間:2023-02-05 02:09:04 RSSフィード2023-02-05 02:00 分まとめ(9件)