😸

マイクロサービスにおける認証・認可を考える

2024/01/10に公開

はじめに

マイクロサービスはサービス単位で機能を分割します。
それによって、他のアプリケーションへ使い回すようにできるなど多くのメリットをもたらします。
一方で、分割することで発生するのが認証・認可の情報をどのように各サービスへ伝えるかということです。
そこで、今回はマイクロサービスにおける認証・認可の方式を見ていき、それぞれの利点や欠点についてみていきます。
なお、認証部分についてはAuthentication and Authorization of End User in Microservice Architectureをかなり参考としています。
気になる方は是非こちらを読んでいただければ幸いです。

認証・認可機能を持つアプリケーションの種類

モノリシックなアプリケーション

モノリシックなプロジェクトは機能を一つにまとめたものなので、認証・認可周りの機能も合わせて担うことになります。
マイクロサービスとは? 今さら聞けないDX関連用語をわかりやすく解説 より一部抜粋
マイクロサービスとは? 今さら聞けない DX 関連用語をわかりやすく解説 より一部抜粋
また、ID やアクセス管理、レスポンス内容の決定やリクエストの振り分けも行います。
モノリシックなプロジェクトでは、HTPP 条件下で基本的にサーバー側で生成されたセッションを使用して、それを元にユーザーを扱います。
なので、流れとしては以下のようになります。
① 認証情報をクライアントからサーバーに送る
② サーバーは認証情報を検証する。
③ 検証が成功すれば、セッションを生成する
③’失敗した場合は再認証を求める。
④ クライアント側のリクエスト・レスポンスにセッションを付与する。
⑤ 付与されたセッションを元に返す値をサーバー側は設定する。
このモノリシックなプロジェクトで行う認証・認可管理は、何より構築が非常に簡単です。
また、システムの流入先が少ないことも利点です。
ただし、多くの課題はあります。
まず、セッションはインメモリに保存する必要があるため、メモリの消費などをもたらします。
これは、渡す情報を小さくするなどの対応でどうにかなりそうですが認証・認可周りの管理とその他の機能が一緒くたになっていることは大きな問題になりえます。
アプリケーション本来の機能と認証・認可が密結合になってしまい、拡張性や柔軟性が著しく下がってしまいます。
すなわち、そのアプリケーションでしか使えないものを生み出してしまうので、似たような案件に対してそのまま活かせないことになります。
さらに、システムの負荷が上がるとロードバランサーが導入されますが、複数のサーバーを設定されたときセッションを共有する方法についても考慮が必要となります。

分散セッション管理によるアプリケーション

セッションを分散管理すると複製のサーバーで同様のセッションを持つようになるので、どのサーバーに対してのリクエストでも同様のセッションを持つようになります。
さらにセッションの複製や中央集権的なセッション管理も行うことができます。
これらによって、モノリシックなアプリケーションで言及したロードバランサーなどを導入した時のセッション共有といった問題が解消されます。
ただし、この分散セッションを構築するのは複雑で、メンテナンスすることが少し難しいものとなっています。

トークンベースのアプリケーション

ユーザー名とパスワードなどを使って認証が完了したら、トークンを発行します。
このトークンを受け取ったクライアントは以降、サーバーに対してトークンを渡すことで必要なレスポンス情報を取得します。
トークンベースの認証とは? 仕様とJWTのメリット、デメリット より引用
トークンベースの認証とは? 仕様と JWT のメリット、デメリット より引用
トークンにしておくとセッション管理を行う必要がなく、サーバー側での扱いも楽になります。
加えて、クライアント側の種類が増えたとしても特定の形式に基づいたトークンさえ、クライアントが取得できれば、これまで通り必要なレスポンスをサーバー側から取得できます。

マイクロサービスにおける認証を考える

認証・認可の管理は誰が行う

まずは認証・認可の管理を各マイクロサービスが行う場合について考えます。
具体的には、ユーザーの判定や権限の管理機能を持ちつつ、自前のデータベース、もしくは認証・認可用のデータベースを共有して使用する方式となります。
この方法はモノリシックなプロジェクトの場合と似ており、通常のアプリケーション機能に加えて認証・認可の機能も有することになります。
利点としては何より別サーバーを立てたりせず、実装にとりりかかりやすいことがあげられます。
ただし、多くの問題も抱えています。
一つ目は新しいサービスを作成した時に、全く同じ機能を搭載しないといけないという手間がかかります。
そのため、何かしらの仕様変更があった際に全てのサービスに対して同じ適用を行う必要があります。
二つ目はデータベースを共有した場合、データベースにエラーが発生すると全てのサービスが停止してしまう問題があります。
三つ目は各サービスに同じ機能を搭載すると、マイクロサービス構築の原則である単一責任の原則から外れてしまいます。
そのため、マイクロサービスの場合、認証・認可の管理はそれ用のサービスを立てることが推奨されます。
そして、認証・認可機能を持たないサービスはそのサーバーとやり取りをすることで、権限による実行管理などを行います。
Authentication and Authorization of End User in Microservice Architecture より引用
Authentication and Authorization of End User in Microservice Architecture より引用
これによって、認証・認可サービス以外のサービスは必要なビジネスロジックに集中することができ、サービス間の疎結合も保てるようになります。

認証・認可サービスの認証機能には何が求められるのか

先程マイクロサービスにおいては、認証・認可サービスを作成することが推奨だと述べました。
では、認証機能を考える際に必要なことについてもう少し見ていきます。
具体的には下記のことを考えます。

  • 安全にユーザー識別ができること
  • 自身が構築したマイクロサービス間でのみ、情報のやり取りが行えること。
  • 複製のインスタンスで動かせるようにステートレスなサービスであること。
  • 単一障害点(SPOF)とならないこと
  • ログアウト機能があること
  • 実装が簡単で、他のマイクロサービスと統合できること。

以上がマイクロサービスで認証機能を考える際に考慮することとなります。
それでは、これを踏まえて紹介されている方法を見ていきます。

分散セッションによる管理

認証・認可サービス経由でログインを行った後、セッションを保存・管理するストアに認証情報を格納し、他のサービスはそのストアから情報を取得しする方法です。
Authentication and Authorization of End User in Microservice Architecture より引用
Authentication and Authorization of End User in Microservice Architecture より引用
この方法はモノリシックなプロジェクトから考えるとイメージが比較的しやすく、多くの認証・認可サービスに備えるべき機能を持っています。
しかし、実装のしやすさと単一障害点(SPOF)とならないということには課題を残すことになります。
ストアを構築するのはもちろんですが、ストア自体が機能しなくなると認証情報のやり取りが行うことができません。
そのため、すでにセッションを用いたシステムを構築しており、ストアが障害発生時に対応できるようにしている場合、選択肢となりえます。

SSO サーバーを用いた管理

シングルサインオン(SSO)は一回の認証で複数のアプリケーションへのアクセスを可能にする仕組みとなっています。
一度認証したら、トークンなどを付与するなどを行い、再度認証せずともアプリケーションを使用できます。
**シングルサインオン(SSO)認証とは?仕組み、認証方式の種類、メリットや認証連携のパターン より引用**
シングルサインオン(SSO)認証とは?仕組み、認証方式の種類、メリットや認証連携のパターン より引用
具体的には、最初だけ認証・認可サービスでログインを行います。
以降は、サービスへアクセスするときにトークンなどを用いて、認証・認可サービスへ認証されているかを問い合わせます。
問い合わせた結果、認証している場合はそのままアクセスし、認証していない場合は再度認証・認可サービスへリダイレクトさせ、認証するように促します。
この方式は先程の分散セッションと異なり、別途セッション用のストアを立てる必要はありません。
そのため、他のサービスは認証しているかをストアに接続する必要がなくなり、認証・認可サービスへ直接問い合わせるだけでよくなります。
個人的な感想ですが、別途ストアに接続するより、認証・認可サービスが認証しているかを確認する窓口を作成、他のサービスはそれを呼び出すだけで認証しているかを判断するほうが実装は容易に思えます。
しかし、この方式は単一障害点(SPOF)に課題を残しています。
様々なサービスは都度認証・認可サービスへ問い合わせを行うことが前提となっているので、仮に認証・認可サービスが止まってしまった場合、他のサービスは一切動かせなくなります。
なので、認証・認可サービスは必ず動いている必要があり、まさに単一障害点(SPOF)となっています。
ちなみに余談ですが、シングルサインオンは先程提示した画像の方式以外にも多くの方式があります。
具体的には、以下の通りです。

  1. エージェント方式
  2. リバースプロキシ方式
  3. 代理認証方式
  4. フェデレーション方式
  5. 透過型方式

詳細は、シングルサインオン(SSO)認証とは?仕組み、認証方式の種類、メリットや認証連携のパターンなどで別途ご確認いただきたいのですが、この記事で添付した画像はあくまでシングルサインオンの一方式だということをご留意ください。

クライアントサイド JWT を用いた管理

SSO サーバー方式を用いた場合の問題点として、単一障害点が発生してしまうことを挙げました。
認証機能を一つのサービスが管理することになると、どうしても単一障害点の問題が出てきます。
そこで、下記画像でクライアントにトークンを渡す際、トークン自体に情報を含めることで都度問い合わせを行う必要を無くす方式が存在します。
**トークンベースの認証とは? 仕様とJWTのメリット、デメリット より引用**
トークンベースの認証とは? 仕様と JWT のメリット、デメリット より引用
その方法の一つが、JSON Web Token(JWT)を用いた情報のやり取りです。
JWT の中に必要な情報を含めてしまうことで、認証・認可サービスは一度トークンさえ発行してしまえば、仮に認証・認可サービスが止まってしまったとしても、他のサービスとのやり取りを行えます。
なので、認証・認可サービスが必ずしも単一障害点とはなりえません。
また、実装の難易度もシングルサインオンの時とほぼほぼ一緒で、認証・認可サービスはトークンを渡す時に署名を行うことと、検証用の鍵を他のサービス用に提供することのみ異なります。
上記機能については、Typescript は Javascript の場合jsonwebtokenを始めとした多くのモジュールが存在します。
そのため、特段実装の難易度が上がるわけではありません。
以上のことから、これまでで関門となっていた単一障害点・実装の難易度どちらも大きな問題ではなさそうなので、クライアントサイドでの JWT で認証機能を実装するのが最善だと感じます。
しかし、この方式はログアウト機能を搭載する際に課題があります。
認証・認可サービスはトークンである JWT さえ渡してしまえば、基本的にはお役ごめんです。
そのため、仮にログアウトを行ったとしても発行した JWT 自体は有効期限が切れるまでは、他のサービスに対して使用することができます。
認証・認可サービス側から、発行した JWT を無効化することはできないのです。
ログアウトした際に JWT を無効化できないなら、他のサービスが JWT を受け取った時認証・認可サービスに問い合わせることで、現在も JWT が有効かをチェックすれば先程の問題は解消します。
ただ、それを行うと散々出てきた単一障害点の問題が立ちふさがります。
以上のことから、ログアウトのことを考えないのであればクライアントサイド JWT は非常に良い選択肢となりますが、ログアウトまで考えると単一障害点という課題が残ります。

JWT+API Gateway を使用した管理

JWT を使ったトークンのやり取りは、ログアウトのことさえ考えなければいい線まで行っていました。
ただ、ログアウトのことを考えるとトークンの無効化が上手くできないという問題を抱えていました。
その対応方法として、JWT に加えて API Gateway を用いて、認証・認可サービスから受け取った JWT を API Gateway で変換し、その情報を使用して他のサービスとのやり取りする方法について見ていきます。
**Microservices における認証と認可の設計パターン より引用**
Microservices における認証と認可の設計パターン より引用
この方法は認証・認可サービスがトークンを発行するところまで、クライアントサイド JWT の場合と同じです。
しかし、受け取る先がクライアントではなく、一旦 API Gateway に渡すようにします。
API Gateway は受け取った JWT をそのまま渡すのではなく、他のキーと紐づけて保存し、キーをクライアントへ渡すようにします。
クライアントは他のサービスへアクセスしたい場合、そのキーを API Gateway に渡し API Gateway はキーから対象の JWT を取得します。
そして、JWT をサービスに渡し、サービスは JWT を検証することでアクセスが可能かを判断します。
この API Gateway を挟む方法は、JWT を管理する主体を API Gateway にします。
API Gateway はさらにクライアント側ではなく、サーバー側なのでログアウトの問題を解消できます。
仮にログアウトした際、API Gateway で保存したキーと紐づいた JWT を削除さえすれば、トークンを無効化できるためです。
また、JWT 周りの管理を API Gateway に集中させることで API Gateway が単一障害点になってしまう問題は一応ありますが、他のアプリケーションが原因で認証・認可サービスが使用できなくなった場合などの影響は大きく受けません。
なので、単一障害点の問題は完全に解消されたわけではありませんが、これまでのものよりも影響を受ける可能性がぐっと低くなっています。
以上のことから、JWT+API Gateway は最善の方法に見えますが、実装の難易度が立ちふさがります。
仮に、これまで見てきた方法を一から組み立てるとした場合、一番やることがおおいのが今回の方法です。
API Gateway を設定するのはもちろんのこと、JWT 発行の仕組みや JWT とキーを紐づけて保存するためのデータベースなどを設定するなど、作業量としては結構多いです。

で、結局どれを選べばいいの?

結論としては、現場がどの方式を選ぶのか決めるとなります。
何とも投げやりですが、正直そうとしか言えません。
仮に、モノリシックなプロジェクトが進んでおり、セッションを用いた管理のノウハウが豊富な場合、セッションを用いるのが一番早く、質の高いものができます。
シングルサインオンにしても、認証・認可サービスのスペックが他のものより圧倒的によく、障害対策も相当力を入れている場合は下手に分散させるよりも、認証・認可サービスに任せてしまう方が良いかもしれません。
クライアントサイドで JWT を使用する場合も、ログアウト機能を搭載しないアプリケーションであれば、実装はとても簡単になります。
それでいて、サービスを増やしたとしても、認証・認可サービスはただ JWT を発行するだけなので、それほど問題にはなりません。
このように、案件などによって必要なものや前提が異なるためどれが一番よいかを決めることはできません。
といった前置きはありつつも、どれか一つに絞れと言われたら私は「JWT+API Gateway」の方法を選びます。
理由としては、最も汎用的に拡張ができるからです。
今後どういった機能が搭載されるかは大体不明なので、何かしらの要望に対しても一番対応しやすいのが「JWT+API Gateway」の方法だと私は考えます。
皆さんはどう思いますか?
良ければコメントください。

マイクロサービスにおける認可を考える

認可についてはMicroservices における認証と認可の設計パターンの「認可の設計パターン」やAuthentication and Authorization in Microservices以上のことを書けないので、そちらを参照してください。
ただ、ざっくり記載すると中央集権的に管理すると各サービスはビジネスロジックだけを考えるだけでよくなるけど、サービスに依存してしまう課題が残ります。
一方で、分散管理するとサービスごとに幅広く権限を振り分けることができるが、共通の権限を振り分ける場合実装が重複してしまう課題があります。
なので、認可の管理は認証以上に案件ごとによって最適解が異なります。
大体は上記の結論になりますが、具体的なことや対応方法の例は冒頭に添付したリンクから確認お願いします。

おわりに

今回はマイクロサービスにおける認証・認可について確認しました。
どれも一長一短があり、銀の弾丸はないのだと改めて実感しました。
各アプリケーションに合わせて、最もよいものを選択できるようにしていきたいと思います。
ここまで読んでいただきありがとうございました。

Discussion