こんにちは、上坂(@takashiuesaka)です。
ちょっとお久しぶりになってしまいました。今日はAzure EventHubsの興味深い機能について書きたいと思います。
EventHubsは様々なデバイスからイベントデータと呼んでいる情報(文字列をバイナリにシリアライズしたもの)を大量に受け付けることができますが、ここで気になることがあります。
- 認証済みのデバイスからだけのイベントを受け付けるように制限したい
- 不正なデバイスからアクセスがあった場合はイベント受付を拒否したい
- どのデバイスからのイベントなのか判別したい
これらの要件を満たすために、EventHubsはShared Access Signature(SAS)とPublisherを組み合わせるやり方が用意されています。
まずこの図をご覧ください。特に何も設定しないでEventHubsにイベントデータを飛ばした場合のイメージ図です。

イベントデータを受け付ける箇所は1か所です。Publisherを使うとこのようになります。

このPublisherというリソースは事前に用意する必要はありません。EventHubsのエンドポイントを次のようにすることで自動的に用意されます。
sb://<SERVICE_NAMESPACE>.servicebus.windows.net/<EVENTHUBS_NAME>/publishers/<PUBLISHER_NAME>
<PUBLISHER_NAME>には好きな文字列を設定できるのですが、デバイスごとの任意のIdをセットすることで、デバイスごとにイベントデータを受け付ける受信機が用意されることになるわけです。
しかし、このデバイスごとの受信機にイベントデータを受け付けてもらうためには次の条件を満たす必要があります。
- 受信したイベントデータとともに署名されたToken(SASToken)が認証データとして付与されていること
- 署名されたToken(SASToken)は、共有アクセスポリシーのKEYを使って署名されていること
共有アクセスポリシーのKEYとは、管理ポータルのEventHubs「構成」で設定したものです。「構成」では管理・送信・リッスンの3種類の権限を選択できますが、署名に使用する共有アクセスポリシーは管理または送信権限が必要です。イベントデータを送信するんですから当たり前ですね。(管理権限は、送信権限を含んでいます)
では、SASTokenの生成方法をご説明・・・する前になぜToken渡すことで認証済みである、と言えるかを考えてみましょう。まずSASTokenを生成するためには次の情報が必要です。
- 管理ポータルのEventhubs「構成」で設定する共有アクセスポリシーの「ポリシー名」
- 管理ポータルのEventhubs「構成」で設定する共有アクセスポリシーの「キー」
- PublisherのリソースURI(上記のsb://<SERVICE_NAMESPACE>・・・)
- 有効期限
これらの情報を使って作られたSASTokenには、次の情報が含まれます。
- 管理ポータルのEventhubs「構成」で設定する共有アクセスポリシーの「ポリシー名」
- PublisherのリソースURI(上記のsb://<SERVICE_NAMESPACE>・・・)
- 有効期限(UNIX時間)
- 署名
ご覧のように共有アクセスポリシーの「キー」を使用して生成された署名が含まれています。Publisher側では送信されたSASTokenを共有アクセスポリシーの「キー」を使って同じように署名を生成して送信されたSASTokenと一致するのかを確認します。
同じ署名を生成できた=共有アクセスポリシーのキーを使用した正しいTokenを知っている=正しいデバイス
ということで、正しいデバイスからのアクセスかどうか判断しているわけです。
では、各デバイスがSASTokenを作るようにすればいいんだな、と思ってしまうかもしれませんが、それだと共有アクセスポリシーの「キー」をデバイスに配ることになってしまうのであまり良いやり方とは言えません。デバイスには事前に作成したSASTokenを配るようにしてください。その時に重要なのは有効期限です。有効期限をいつまでにするのかは業務要件によるところです。例えば1年契約のアプリケーションなのであれば1年間(より少し長め)にする、などです。
さて、ではSASTokenの作り方です。
| var serviceNamespace = "<SERVICE_NAMESPACE>"; | |
| var eventHubsName = "<EVENTHUBs_NAME>"; | |
| var deviceName = "myDevice"; // 認証ユーザーのIdとかをセット | |
| // 管理ポータルで設定した共有アクセスポリシーの情報 | |
| var sharedAccessPolicyName = "<NAME>"; | |
| var sharedAccessPolicyKey = "<KEY>"; | |
| // <EVENTHUBS_NAME>/publishers/<PUBLISHER_NAME> | |
| var resourcePath = String.Format("{0}/publishers/{1}", eventHubsName, deviceName); | |
| // sb://<SERVICE_NAMESPACE>.servicebus.windows.net/<EVENTHUBS_NAME>/publishers/<PUBLISHER_NAME> | |
| Uri publisherUrl = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, resourcePath); | |
| // SASTokenの生成 | |
| string sas = SharedAccessSignatureTokenProvider.GetSharedAccessSignature( | |
| sharedAccessPolicyName, | |
| sharedAccessPolicyKey, | |
| publisherUrl.AbsoluteUri, | |
| TimeSpan.FromSeconds(60)); |
特に難しいことはしていません。PublisherまでのURIを構築して、共有アクセスポリシーのNAMEとKEY、有効期限をSharedAccessSignatureTokenProviderクラスのGetSharedAccessSignatureメソッドに渡すだけです。
ではこのSASTokenを使ってイベントを送信しましょう。AMQPでやってみます。
| static void SASを用いたデバイス認証によるデータ送信() | |
| { | |
| var serviceNamespace = "<SERVICE_NAMESPACE>"; | |
| var eventHubsName = "<EVENTHUBs_NAME>"; | |
| var deviceName = "myDevice"; // 認証ユーザーのIdとかをセット | |
| // 管理ポータルで設定した共有アクセスポリシーの情報 | |
| var sharedAccessPolicyName = "<NAME>"; | |
| var sharedAccessPolicyKey = "<KEY>"; | |
| // <EVENTHUBS_NAME>/publishers/<PUBLISHER_NAME> | |
| var resourcePath = String.Format("{0}/publishers/{1}", eventHubsName, deviceName); | |
| // sb://<SERVICE_NAMESPACE>.servicebus.windows.net/<EVENTHUBS_NAME>/publishers/<PUBLISHER_NAME> | |
| Uri publisherUrl = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, resourcePath); | |
| // SASTokenの生成 | |
| string sas = SharedAccessSignatureTokenProvider.GetSharedAccessSignature( | |
| sharedAccessPolicyName, | |
| sharedAccessPolicyKey, | |
| publisherUrl.AbsoluteUri, | |
| TimeSpan.FromSeconds(60)); | |
| var devideTokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(sas); | |
| var factory = MessagingFactory.Create(ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, ""), new MessagingFactorySettings | |
| { | |
| TokenProvider = devideTokenProvider, | |
| TransportType = TransportType.Amqp | |
| }); | |
| // イベント送信用のクライアント | |
| var client = factory.CreateEventHubClient(resourcePath); // ←このpublishers/<PUBLISHER_NAME>が、SAS作成時の<PUBLISHER_NAME>と一致している必要がある | |
| var data = new EventData(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { message = "どうもこんにちは!" + DateTime.Now }))); | |
| // PartitionKeyにデバイス名を入れておいて、後で判別に使う | |
| data.PartitionKey = deviceName; | |
| client.Send(data); | |
| } |
ポイントは、SASTokenを作るときのリソースのURIと、イベントを送信する先のURIはどちらも
sb://<SERVICE_NAMESPACE>.servicebus.windows.net/<EVENTHUBS_NAME>/publishers/<PUBLISHER_NAME>
であるようにすることだけです。
これで認証済みデバイスからのイベント送信が実現できました。続きは長くなったのでまた次回!