この記事はRetty Advent Calendar 2019 21日目の記事です。エンジニアの 神@pikatenor がお送りします。11日目の記事に書かれた「弊社エンジニアの神(注・人名であり実名です)」とは私のことです。 qiita.com
さて世はまさにマイクロサービス大航海時代、大規模化した組織・肥大化したコードベースのメンテナンスを継続的に行っていくべく、アプリケーションを機能別に分割する同手法が注目を集めていることは皆さんもご存知でしょう。 マイクロサービスアーキテクチャ特有の設計課題はいくつかありますが、今回は認証情報のような、サービス間でグローバルに共有されるセッション情報の管理のパターンについて調べたことをまとめてみたいと思います。
背景
HTTP は本質的にステートレスなプロトコルですが、実際の Web サービス上では複数リクエストをまたがって状態を保持するために、Cookie や Authorization ヘッダを用いてセッション情報をリクエストに紐付けることが一般的です。
モノリシックな Web アプリケーションの場合、どこかのタイミングでユーザーからのログイン要求に応え、サービス自身や外部 ID プロバイダとのやりとりを通じてユーザー認証情報をセッションストアに保存し、以降そのセッション情報に紐づくセッション ID を付与されたリクエストを送ってきたクライアントをそのユーザーのものとして処理することになります。
一方、マイクロサービスアーキテクチャでは各サービスが独立してリクエストを処理する形になるため、セッション情報をいかに共有するかが課題となります。
パターンあれこれ
というわけで、いくつか典型的な設計方針を集めてみたのでそれらの概要を紹介していきます。
1. 独立型 - SSO
それぞれのマイクロサービスにサービス全体の認証を行うマイクロサービスを一つおき、その他のサービスには独立にセッションストアを持たせ、クライアントはそれぞれのマイクロサービスに対し個別に認証を行うパターンです。サービス全体として特定の API Gateway は持たず、 クライアントが直接マイクロサービスとやりとりする場合向けの設計だと思います。サービス間の独立性は高まりますが、その分クライアント側ではリクエスト先ごとにセッション ID を管理しなければなりません。認証を行うサービスにログイン検証の負荷が集中するのも難点です。
2. 中央集権型
①の独立型と似ていますが、セッションストアを共通化したパターンがこちらです。認証サービスがユーザー認証情報をセッションストアに保存し、各マイクロサービスはセッション ID を用いてセッション情報をストアから読み出します。この場合クライアントから見たログイン回数は1度になるほか、セッション ID のリボークも容易になるため、セキュリティインシデント時の対処もしやすいです。一方セッションストアは多くのサービスからのリクエストを受けるため、高い可用性が求められます。
3. 分散型 - Client Token
特定のセッション ID ・ストアを使用せず、クライアントから送られるリクエストそのものにセッション情報を埋め込むパターンです。JWT エンコードによる署名付き JSON や、Rails の Cookie Store などがよく知られています。マイクロサービス自身で認証情報の検証が完結できるため、SPOF の回避につながります。問題点としては、一度発行してしまったトークンはリボークがほぼできないこと、JWT の場合内容そのものは暗号化されていないため、機密情報は保持できないことが挙げられます。
4. ゲートウェイ分散型 - Opaque Token
マイクロサービスの入り口に API Gateway やロードバランサを設けるパターンは多いかと思います。これらゲートウェイに先ほどの③の Client Token との変換機能を持たせ、クライアントにはセッション情報とは紐付かないトークンを発行し、サービス間では分散型セッションを用いるパターンです。JWTであった機密性の問題の対処となりますが、LBのようにリクエストの受け手が複数の場合でも一意に変換できる必要があること、変換テーブルを一元管理する設計にした場合②中央集権型と同様の SPOF を抱えることになります。
まとめ
概要に述べたいくつかの観点から表にまとめてみるとこんな感じです。
* | 独立型 | 中央集権型 | 分散型 | ゲートウェイ分散型 |
---|---|---|---|---|
可用性 | 認証サービスに集中 | セッションストアに集中 | ◯ | ? 変換方式による |
機密性 | ◯ | ◯ | × | ◯ |
状態管理 | ×各サービスが責任もつ | ◯ | ◯ | ◯ |
リボーク問題 | ▲実装困難 | ◯ | × | △可用性犠牲 |
また、この記事では充分に扱うことができませんでしたが、グローバルセッション情報の肥大化への対処や、サービス間認証、脆弱性の作り込みの回避など、考慮すべき点は多岐にわたります。
実際のアプリケーションの要件に応じて細部もかなり異なるものとなるでしょうし、なかなか一筋縄ではいかないアーキテクチャ設計ですが、こうした設計を実アプリケーションにうまく適用していくのがエンジニアの腕の見せ所のように思います。やっていきましょう。