エムスリーエンジニアリンググループの遠藤です。
現在、私はBIRチームに所属しています。 BIRのエンジニアは主にアンケート関連システムの開発を行っています。
先日、同チームの岩本さんからアンケートシステムにJWTを利用した話のエントリーがありました。
この記事では、同様のテーマで、採用した署名付きURLについてもう少し詳しく書いてみたいと思います。
導入経緯
先のエントリーでも説明がありましたが、簡単にシステム概要を説明します。
BIRのアンケートは、基本的に配信システムであるDolphinを経由して実施されます。 一旦Dolphinに流入し、そこで認証を行ったのち、各アンケートシステム(Tiger,Coyoteなど動物の名前が多いです)へリダイレクトすることでアンケートが開始します。
先のエントリーにあるように、各アンケートシステムはM3の認証基盤とは接続しない方針のためユーザー認証を行う事はできませんが、 アクセスが不正でないことは担保する必要があります。
実現したいのは、アクセスユーザーの認証ではなく、アクセス自体の認証(Dolphinからの流入しか受け付けないこと)であるため、 これを実現する方法として署名付きURLを採用しました。
署名付きURLとは
そのままですが、署名となる情報が含まれるURLのことです。
署名付きURLでアクセスされるシステムはこの署名を検証することで正式なシステムから発行されたリクエストURLであることを証明できるため、 利用ユーザーは制限されたリソースへのアクセスを認証(あるいは認可)処理なしでアクセスを実現できます。 AWSのS3やGCPのCloudStorageでは制限されたリソースへのアップロードやダウンロードを行う方法として採用していますね。
今回アンケートシステム間の連携に利用する署名には、すでに記載している通り、Json Web Token(JWT)を選択しました。 もともとは共通鍵を利用したハッシュ値の照合による署名検証を検討していましたが、
- 非対称鍵であれば、各アンケートシステムはシークレット管理が不要
- 各アンケートシステムはDolphinの利用するキーペアの公開鍵を利用するのみ
- クラウドプロバイダの提供する認証(認可)の仕組みを利用する場合、エンドポイントから公開鍵の取得が容易に可能
- GCPではサービスアカウントの公開鍵取得エンドポイントが用意済
- JWTの標準クレームに則れば、一般的なライブラリで作成・検証可能
といった理由からJWTを選択しました。
Json Web Token(JWT)
ご存じの方には不要と思いますが、JWTについて簡単に説明しておきます。
JWTはRFCで規定されたJSONデータ構造で表現したトークンの仕様です。
JWTの構成はざっくり説明すると以下のように3つの要素を.(ドット)でつなげた構造になっています。
[(1)ヘッダ].[(2)ペイロード].[(3)署名]
それぞれのデータには
(1)ヘッダ
アルゴリズムや鍵の情報を含んだjsonデータをBase64エンコードしたもの
(2)ペイロード
表現したいjsonデータ構造をBase64エンコードしたもの*1
(3)署名
[(1)ヘッダ].[(2)ペイロード]
を(1)に記載されたアルゴリズムおよび秘密鍵で署名しBase64エンコードしたもの
が入ります。
発行側が秘密鍵を使用して発行したJWTに対し、検証側は、[(3)署名]
と公開鍵を利用して生成したデータと、[(1)ヘッダ].[(2)ペイロード]
から生成したデータを比較することで検証を行います。
これにより、発行元が想定するシステムであること、データが改ざんされていないことが保証できます。
今回のケースでは、発行側がDolphin、検証側が各アンケートシステムなので、各アンケートシステムはDolphinの公開鍵だけ知っていれば署名の検証が可能になるため、シークレットを持つ必要がありません。
JWTクレームの設計
BIRの各アンケートシステムがアンケートを開始するには
「誰(アクセスユーザーの識別子)」が「どのアンケート(アンケートの識別子)」に回答しようとしているか
を知る必要があるため、これをJWTのクレームに盛り込んでいます。
JWTのクレームはRFCで規定されている標準クレームにできるだけ沿うように以下のように設計しました。
{ iss: [発行者(Dolphin)], sub: [主体(アクセスユーザーの識別子)], aud: [利用者(利用するシステム)], exp: [JWTの有効期限日時], nbf: [JWTの有効化開始日時(今回は発行日と同じ)], iat: [JWTの発行日時], jti: [JWTの識別子] cid: [アンケート識別子] }
DolphinはGCP上に構築しているため、Dolphinの利用するGCPサービスアカウントをiss(発行者)とし、署名にはその鍵を利用しています。
一点、アンケートの識別子を管理する場所としてcidというクレームのみ新たに定義しました。(弊社CTOからの指摘により判明しましたが、このクレーム名はパブリッククレームと衝突する可能性があるため、プライベートクレームとしては推奨されない命名*2でした。。。定義の際はご注意ください。)
それ以外は標準クレームに則っているため、 検証側の各アンケートシステムは一般的なJWTライブラリ*3を利用すれば、有効期限などのクレームチェックも含めた署名検証を一括で実現できます。 あとは、各システムが必要に応じてaudやsub、cidなどのクレームの追加検証を行います。
検証後のJWTは改ざんがされていないことが保証されるため、 subとcidから取り出したアクセスユーザーの識別子とアンケート識別子を信頼してアンケートを開始できます。
署名付きURLという設計選択は適切だったか
現状の利用用途に限れば、m3.com配下のリダイレクトになるたため、
JWT署名はURLではなくm3.com
ドメインのCookieに入れてしまうほうが適切かもしれません。
m3.comドメイン全体に送られてしまう可能性があるという問題はありますが、Cookieであれば不用意に署名情報を晒さずすむぶんメリットがあると考えられるからです。
一方で、Cookieを選択するとドメインの制約を受けるため、
m3.com
以外のドメインへのリダイレクトを検討しなければならなかった場合、対応できません。
署名付きURLであればその制約を受けない点がメリットと言えます。
このトレードオフを考慮した上で、チームの方針として、仕様の柔軟性が高い署名付きURLを選択するに至りました。
所感
アクセスの認証方法として採用したJWT署名付きURLについてまとめてみました。
実際に設計・実装してみて
- JWTの設計をイチからやるのは初めてだったが、標準に則ることによりエコシステムがあるため実装が楽。
- 署名付きURLは利用用途が広いわけではないが、ボディやヘッダに署名を入れられない制約下では検討の余地がある。ただし、他の案とのメリット・デメリット整理が大事。
と感じた次第です。
We're hiring!
エムスリーではアンケートシステムを一緒に開発してくれるエンジニアを募集しています。 興味を持たれた方は下記よりお問い合わせください。