話題の記事

AWS Dev Day Tokyo 2018 で「マイクロサービス時代の認証と認可」の話をしてきた #AWSDevDay

マイクロサービスが話題を集め、コンポーネントの急速な API 化が進んでいます。 認証や認可は、主にエンドユーザとシステムの間の問題だと認識されますが、今やコンポーネント間のサービス呼び出しにおいても重要な役割を担っています。 複雑に入り組んだマイクロサービス間の認証と認可について、実際に開発している API プラットフォームの実例を元に、実践的な知見をお伝えします。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

緊張すると声がヤムチャになる都元です。このセッションの自己紹介でアムロ・レイとか言って盛大にスベったので切り替えていきます!

さて、1週間の時間が経ってしまいましたが、去る 11/2 (金) に目黒セントラルスクエアにおいて「マイクロサービス時代の認証と認可」と題しましてお話をさせていただきました。

スライド

認証と認可の基礎知識

繰り返しになりますので、過去エントリーよくわかる認証と認可 | DevelopersIO を参照してください。

また、Web API のアクセス制御としては、だいたい次の4つくらいが頭に浮かぶと思います。

  • Basic 認証
  • Digest 認証
  • リクエスト署名
  • OAuth 2.0

この辺りは Spring Day 2016 - Web API アクセス制御の最適解でお話したことがありますので、こちらも併せてご覧ください。

Coffee break: 都元、日頃のお仕事

prismatix を作ってます!

prismatix は、EC (要するにネット通販) と CRM (要するに顧客満足度とロイヤルティの向上を目的としたシステム) のために必要な機能を Web API 群として提供する製品です。

まぁ、お問い合わせはこちらからどうぞ!

ユーザーの認証とシステムの認証

さて、認証と言うとまず思い浮かぶのがユーザーの認証です。つまり、そもそもこのAPIリクエストの発生源となった操作を、誰が行ったのか? という話ですね。

一方で、リクエストの発生原因が人間の操作ではなく、別システムによるスケジューラーだったりして、システム間でのAPIリクエストが発生する場合もあります。この場合、ユーザーの認証は不可能です。では何も認証しなくてよいかと言うとそんなことはありません。呼び出し元のシステムの認証、つまりクライアントの認証が必要になってきます。

さらに、マイクロサービスのようなAPI サーバーが別のAPIサーバーを呼び出すような仕組みでは、リクエストに対する認証を行う時に、ユーザーの認証クライアントの認証の両方を行う必要があったりします。その上で、ユーザーとクライアント両者の権限 (認可状態) を評価し、アクセス制御を行います。

結局、APIに対するリクエストの認可は、大きく次の2つに分類できます。

  • ユーザーの許諾に基づいて、クライアントがその操作を代行する
  • (ユーザーの後ろ盾無く、) クライアント単独の権限でその操作を実行する

OAuth 2.0 とマイクロサービス玉突きの認証認可

さて、API のアクセス制御にしばしば利用されるのが OAuth 2.0 です。しかしその仕組はとにかく複雑です。

あるリソース (データ) の所有者がユーザーであると位置づけた時、そのデータを実質的に管理・保持するシステム (管理者) と、データを利用・活用するシステム (利用者) を考えます。

多くのシステムでは、管理者と利用者が同一 (仮にアプリケーションサーバーとDBサーバーに分かれていたとしても、その運営主体は同一) です。正直、そのようなシステムにおいては OAuth 2.0 はちょっとオーバースペックだと考えたほうがいいと思っています。

ちなみに prismatix と ECサイトの例では、我々 prismatix がリソースの管理者となり、EC事業者がリソースの利用者となります。このようなケースで、OAuth 2.0 が活きてきます。

ここで。アクセス制御に OAuth 2.0 を採用したマイクロサービスアーキテクチャにおいて、サービス1がサービス2を玉突きで呼び出す状況を考えます。ここまでの文脈では、アクセス制御においては「リクエストの元となる操作を行ったユーザー」と「直接相対するクライアント」の2つを同時に認証しなければならない、という話でした。

つまり、サービス2はユーザーの代理として動くWebサーバーサービス1の両方と、元々の操作を行ったユーザーAのすべてを認証すべきです。ではこの時、間に挟まったWebサーバーというクライアントを、サービス2は認証すべきでしょうか?

この問に対しては、答えはありません。OAuth 2.0 としては特に規定されていない状況となります。これに対する現実的な戦略としては次の2つが考えられます。

  • Access token propagation 戦略: Webサーバーがサービス1を呼び出した時に使ったアクセストークンを、サービス2がサービス2を呼ぶ際にも流用して使う。
  • Client credentials grant 戦略: サービス1がサービス2を呼ぶ時は、(ユーザーの後ろ盾なく、) サービス1単独の権限としてサービス2を操作する。

前者のメリットはサービス2に対する操作も、ユーザーのお墨付きで実施できることですが、デメリットはサービス2は直接相対するサービス1の存在を認証できないことになります。一長一短です。

我々 prismatix のチームでは、API の操作をユーザーのお墨付きのもとで実施することを重視した結果、前者の戦略を採用しています。

OpenID Connect 1.0 とマイクロフロントエンド

ところで、AWS というのはマイクロサービスアーキテクチャのお手本になるサービスです。AWS は基本的にAPIを提供するサービスですが、マネジメントコンソールというUIコンポーネントも提供しています。

この時、マネジメントコンソールというのは1つの大きなアプリケーションなのだろうか?と考えると、おそらくそうではないであろう想像が付きます。そう、フロントエンドも小さなアプリケーションとして分割してあるのではないでしょうか。このようなアーキテクチャをマイクロフロントエンドと呼びます。

マイクロフロントエンドを実現するためには、複数のフロントエンドアプリケーションのトンマナを揃える必要があり、そしてアプリケーション間で SSO (Single Sign-On) を実現する必要があります。

しかし、SSO と言われて素朴に想像するアーキテクチャは、ハマりやすく脆弱なものになりがちです。ナイーブに「アプリA上でログインしたら、アプリBでもログインしたことになる」を実現しようとすると、こうなります。

このアーキテクチャの問題点は次のとおりです。

  • アプリが3つ4つと増えていくにあたって、それぞれすべてがログイン画面と認証処理を実行できる必要がある。ユーザーが最初にどのアプリにランディングするかはわからないからです。
  • 認証状態の共有は、おそらくCookieやそれに準ずる仕組みで実現することになると思います。すべてのアプリを同じドメイン、またはサブドメインでホストしていればCookieによる共有ができますが、異なるドメインが必要になった途端に破綻します。

そこで利用できるのが、OpenID Connect 1.0 (OIDC) です。OIDCでは認証を行うフロントエンドアプリケーションを分離独立させ、各アプリは「認証したい!」と思った時にユーザーを認証サーバーにリダイレクトさせます。認証サーバー上で username / password の確認が終わると、assertion と呼ばれる電子署名付きのデータ (JWT) を再びリダイレクトで返し、各アプリはこの署名検証を行う仕組みです。

このような仕組みであれば、各アプリがパスワード検証処理を持つ必要もありません。また、リダイレクトを用いた仕組みであるため、ドメインをまたいでも認証の連携が可能です。

本当の闇

ただしこの仕組は、それはそれで問題を抱えることになります。

SSO は実現できますが、SLO (Single Log-Out) が難しいのです。アプリAや認証サーバー上でログアウトしたとしても、その他のシステムはその事実を簡単に知ることはできません。何も対策をしなければ、その他のシステム上ではセッションが継続することになります。

というかそもそも、どこでログアウトしたら、それがどの範囲にまで波及すべきかという要件はシステムごとに異なります。まずはその整理から始めなければならないでしょう。

その上で、要件を満たす方法は色々あるにはあるのですが…。それは、また別の話…。