SSO(シングルサインオン)について理解する OAuth2.0 編
こんにちは。
近頃はめっきり濃厚豚骨魚介つけ麺に夢中な Sasakura です。
シングルサインオンについて共有する動機
プロジェクトにおいて AWS の Amazon Cognito というサービスを使用して認証認可に関する設計・実装のタスクを担当させて頂きました。
Amazon Cognito のベースとなっているのが OAuth2.0 という認可に関する仕組みです。
Amazon Cognito の公式ドキュメント は OAuth2.0 に関してある程度知識がある前提で書かれており、おおまかに理解していれば Amazon Cognito や他の認証系サービスを利用するにあたってキャッチアップが容易になるので、備忘録もかねて共有したいと思います。
シングルサインオンとは
1 度のユーザー認証によって複数のシステム(業務アプリケーションやクラウドサービスなど)の利用が可能になる仕組みのことです。
システムごとに ID、パスワード等で認証を行うよりもユーザー・管理者の負担低減やセキュリティの向上が期待できると言われています。
シングルサインオンによく使われる仕組みをざっくり解説
OAuth2.0
認可のためのフレームワークです。
ユーザーが所持するアプリケーション内のコンテンツへ、別のアプリケーションによるアクセスを許可するための仕組み。OpenID Connect
OAuth 2.0 を認証のために拡張させたものが OpenID Connect です。
ユーザーの ID 情報を連携する仕組みでもあります。SAML
Security Assertion Markup Language の略称で、異なるインターネットドメイン間でユーザー認証を行うための XML をベースにした標準規格です。
SAML を利用することで企業の持つアイデンティティ情報、例えば、Active Directory などを利用して、複数のクラウドサービスへのシングルサインオンを実現します。
認証と認可の違い
認証(Authentication) | 認可(Authorization) | |
---|---|---|
概要 | 本人確認のこと。名乗った人物が本人であるかを検証することです。 | 特定条件下において、対象物(リソース)を利用可能にする(アクセス)権限を与えることです。 |
具体例 | ログイン画面で ID・パスワードを使用して本人確認を行う | EC サイトで商品の閲覧は誰でもできるけど、購入するためにはログインが必要 |
OAuth2.0 について解説
具体例
具体的な例に基づいて理解を進めていこうと思います。
あるスケジュール管理アプリがあったとして、そのスケージュール管理アプリに、既に Google カレンダーに登録しているスケジュールを取り込む機能を実装するとします。
取り込むにあたって一番単純な方法は、スケジュール管理アプリにユーザーの Google の ID とパスワードを保存して、アプリがユーザーに代わって Google にログインして、Google カレンダーの情報を取得するという方法です。
問題点
しかし、上記の方法には以下の問題点があります。
- スケジュール管理アプリに移譲する権限が大きすぎる
アプリが Google にログイン出来てしまうと、Google カレンダー以外の様々な機能にアクセス出来てしまいます。 - アプリが悪意のある製作者によるものかもしれない
アプリの製作者に悪意があり、預かった ID とパスワードを利用して不正なアクセスが行われるかもしれません。 - アプリが攻撃を受けた場合、預けた ID とパスワードが流出する恐れがある
アプリの製作者に悪意が無かったとしても、攻撃によって ID とパスワードが盗まれる恐れがあります。
上記の問題点を解決するのが OAuth2.0 という認可の仕組みです。
OAuth2.0 とは
OAuth2.0 の基本仕様である RFC6749 に以下のような記載があります。
OAuth 2.0 は, サードパーティーアプリケーションによるHTTPサービスへの限定的なアクセスを可能にする認可フレームワークである.
引用元:The OAuth 2.0 Authorization Framework
上記の説明を今回の具体例に当てはめると以下のような図になります。
ユーザーは Google Auth という Google の認証サービスでユーザー ID とパスワードを使用して認証を行います。その時に、スケジュール管理アプリが Google Calendar に登録されたスケジュール情報を利用する事に同意します。
ユーザーが同意すると Google Auth は許可証を発行して、スケジュール管理アプリに渡します。スケジュール管理アプリは許可証を持って Google Calendar にアクセスする事で、ユーザーの登録したスケジュール情報を利用することができます。
OAuth2.0 の仕組みを利用すると、ユーザーはスケジュール管理アプリにユーザー ID やパスワードを預ける必要がないため、アプリが攻撃されてパスワードが流出する心配がなくなります。
また、許可証を使用してアクセスできるのは Google Calendar に限られていますし、許可証には有効期限があるため、もしスケジュール管理アプリに悪意のあるプログラムが含まれていても被害を限定することができます。
OAuth2.0 の用語解説
OAuth2.0 を利用する上で押さえておきたい重要なキーワードを解説します。
ロール(登場人物)
OAuth には以下の 4 つのロールが存在します。
リソースオーナー
スケジュール管理アプリのユーザーで、リソースの所有者です。今回の例で、リソースは Google Calendar に登録されたスケジュール情報にあたります。クライアント
リソースサーバーの情報を利用するアプリケーションです。リソースサーバー
リソースオーナーの情報を管理するサーバーです。認可サーバー
リソースオーナーを認証して、クライアントがリソースサーバーの情報を利用するための許可証(アクセストークン)を発行します。
また、認証時にクライアントがリソースサーバーの情報を利用する事に同意するかの確認をリソースオーナーに対して行います。
トークン
OAuth ではトークンと呼ばれる文字列を使用して情報がやり取りされます。
アクセストークン
認可サーバーからクライアントに対して発行される許可証です。
誰がどのリソースに対してどのような操作を行う事が許可されているのか、またその有効期限が含まれます。
今回の例ではスケジュール管理アプリが Google Calendar に登録されたスケジュール情報を利用する事を許可するという情報を持っています。リフレッシュトークン
アクセストークンの再発行に使用されます。
アクセストークンを保持していれば、誰でもリソースサーバーにアクセス出来ます。そのため、アクセストークンの有効期限は比較的短めに設定して、もしアクセストークンが流出した場合でも利用できる時間を限定します。
リフレッシュトークンにはアクセストークンより長い有効期限を設定して、アクセストークンの有効期限が切れたら、リフレッシュトークンを認可サーバーに送信することでアクセストークンを再取得します。
スコープ
スコープとはリソースオーナーがクライアントに付与する権限の範囲です。
エンドポイント
認可エンドポイント
認可サーバーが持つエンドポイント。クライアントは認可エンドポイントにアクセスすることによって認可処理を開始することができます。トークンエンドポイント
認可サーバーが持つエンドポイント。クライアントがアクセストークンを取得するためのエンドポイントです。リダイレクトエンドポイント
クライアントが持つエンドポイント。認可サーバーによってユーザーの認証処理が完了した後にリダイレクトされるエンドポイントです。
グラントタイプ
グラントは付与を意味していて、リソースオーナーからクライアントに権限を付与する手順にはいくつかパターンがあります。想定するシステム構成に合わせてどのグラントタイプを使用するかを選択します。
以下は基本仕様で定義されている 4 つのグラントタイプです。
- 認可コードグラント
- インプリシットグラント
- クライアントクレデンシャルグラント
- リソースオーナーパスワードクレデンシャルグラント
実践
ここからこれまで紹介してきた Oauth2.0 の認可処理を、AWS の Cognito と API Gateway を使用して構築してみたいと思います。
使用するグラントタイプ
アクセストークンを取得するためのグラントタイプは、最も代表的な認可コードグラントを使用します。
認可コードグラントのシーケンス図は以下になります。
リソースサーバーと認可サーバーの構築
APIGateway を使用して API を作成する
まずはリソースサーバーとして使用する API を API Gateway 上に作成します。
「アクション」から「リソースの作成」をクリックし、「リソース名」に「test」と入力して「リソースの作成」ボタンをクリックします。
「アクション」から「API のデプロイ」をクリックします。
「デプロイされるステージ」に「新しいステージ」を選択し、「ステージ名」に「dev」を入力して「デプロイ」をクリックします。
ここで作成した API が呼び出せるか確認するために、ポストマンからリクエストしてみます。/test の URL に GET リクエストを送信するとステータスコード 200 が返ってきていて、API を呼び出せることが確認できました。
Cognito ユーザープールを作成する
次に、認可サーバーとして使用する Cognito ユーザープールを作成します。
※言及のない箇所はデフォルトの設定値を使用します
「セキュリティ要件を設定」の画面では「多要素認証」で「MFA なし」を選択、「セルフサービスのアカウントの復旧」はチェックを外して「次へ」をクリックします。
「サインアップエクスペリエンスを設定」の画面はデフォルトのまま「次へ」をクリックします。
「アプリケーションを統合」の画面では以下の設定を行います。
「Cognito のホストされた UI を使用」をチェックします。
「ドメイン」は「Cognito ドメインを使用する」をチェックして「ドメインプレフィックス」に任意の文字列を入力します。
「アプリケーションタイプ」は「パブリッククライアント」、「アプリケーションクライアント名」は「oauth-test-app」を入力します。
「クライアントシークレット」は「クライアントシークレットを生成しない」をチェックし、「許可されているコールバック URL」は「http://localhost/callback」を入力します。
「高度なアプリケーションクライアントの設定」の中の「OAuth 2.0 許可タイプ」に「認証コード付与」が設定されている事を確認します。
これはグラントタイプの「認可コードグラント」を使用するために必要な設定です。
「次へ」をクリックします。
「確認および作成」の画面では「ユーザープールを作成」をクリックします。
認可サーバーにリソースサーバーを登録する
先ほど作成した API をリソースサーバーとして、認可サーバーである Cognito ユーザープールに登録します。
「リソースサーバーを作成」の画面で、「リソースサーバー」と「リソースサーバー識別子」に「oauth-test-api」を入力します。
「カスタムスコープ - オプション」の「スコープ名」に「read」、「説明」に「読み取り権限」を入力して、「リソースサーバーを作成」をクリックします。
アプリクライアントにカスタムスコープを登録する
Cognito ユーザーを登録する
リソースオーナーであるユーザーを登録します。
Cognito ユーザーのパスワード変更
Cognito ユーザーは、登録後に必ずパスワードを変更する必要があります。
作成された Cognito ユーザーを確認すると「確認ステータス」が「パスワードを強制的に変更」になっています。
以下のような AWS CLI のコマンドを実行してパスワードの変更を行います。
aws cognito-idp admin-set-user-password --user-pool-id ap-northeast-1_********** \ --username sasakura \ --password ******** \ --permanent
コマンドを実行すると「確認ステータス」が「確認済み」になり、ユーザーが使用できるようになりました。
オーソライザーを作成する
続いて API の認可処理を行うためのオーソライザーを作成します。
リソースサーバーである API に対してクライアントがリクエストを送信した際に、リクエストに含まれるアクセストークンを解析してアクセス権限の有無を判断するのがオーソライザーの役割です。
最初に作成した「oauth-test-api」の管理画面を開き、「オーソライザー」タブを開きます。
「新しいオーソライザーの作成」をクリックします。
「名前」に「oauth-test-authorizer」、「タイプ」は「Cognito」をチェック、「Cognito ユーザープール」は先ほど作成した「oauth-test-userpool」を選択し、「トークンのソース」は「Authorization」を入力して「作成」をクリックします。
API にオーソライザーを設定する
API で認可処理を行えるようにオーソライザーを設定します。
「oauth-test-api」の管理画面の「リソース」タブを開きます。
「GET」 → 「メソッドリクエスト」をクリックします。
「認可」に「oauth-test-authorizer」、OAuth スコープに「oauth-test-api/read」を入力します。
認可処理の検証
ここまでで、リソースサーバーと認可サーバーの構築は完了です。
API にオーソライザーを設定したため、アクセストークンを設定せずにリクエストを行うとリクエストが拒否されるはずです。
再度ポストマンでリクエストを送信してみます。
ステータスコード 401 が返ってきていて、オーソライザーによる認可処理でアクセスが拒否されたことが確認できました。
OAuth2.0 によるリソースサーバーへのアクセス
ここからは認可コードグラントの手順に沿って、アクセストークンの取得とリソースサーバーへのアクセスを行います。
リソースサーバーにアクセスするために、アクセストークンの取得を行います。
認可エンドポイントへアクセスする
「認可エンドポイント」にアクセスするために、シーケンス図 3 番の「認可リクエスト」を行います。
ブラウザのアドレスバーに以下のような URL を設定してアクセスします。
https://*************.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize?
client_id=**************2sr2rn2la6mib&
response_type=code&
state=xyz&
scope=oauth-test-api/read
redirect_uri=http%3A%2F%2Flocalhost%2Fcallback
Cognito の認可エンドポイントの詳細はリンク先のドキュメントに記載がありますが、各パラメータの簡単な説明は以下になります。
_client_id_
Cognito ユーザープールに登録したクライアントの ID です。_response_type_
"response_type"に設定した文字列によってグラントタイプが決定します。認可コードグラントを使用する場合"code"を設定します。_state_
パブリッククライアント(web ブラウザ等がクライアントの場合)が認可コードグラントを使用する場合、クロスサイトリクエストフォージェリという手法を使用した攻撃を防止するためにランダムに生成した文字列を設定します。_scope_
リソースオーナーがクライアントに対して移譲する権限の範囲を設定します。"oauth-test-api/read"を設定すること API の"/test"にアクセス出来るようになります。_redirect_uri_
認可サーバーからクライアントにリダイレクトする時(シーケンス図 8 番「認可レスポンス」)の URI です。事前登録したものを設定します。
認証情報を入力する
認可エンドポイントにアクセスすると、以下のようなログイン画面が表示されるのでユーザー名とパスワード入力して、「Sign in」をクリックします。
本来であれば「Sign in」をクリックした後で、以下のようなリソースオーナーからクライアントへ権限を移譲することの確認画面(シーケンス図 6 番「権限移譲の確認画面」)が表示されて欲しいのですが、Cognito では表示されません。
※ 以下の画像は Google Auth の認可サーバーによる認証を行ったときのスクリーンショットです
クライアントへのリダイレクト
認可サーバーによる処理が完了すると、シーケンス図 3 「認可リクエスト」の際に設定した"redirect uri"にリダイレクトされます。
URL には上記のように"code", "state"のパラメータが設定されており、"code"に設定されている文字列が認可コードです。この認可コードを認可サーバーのトークンエンドポイントに送信することでアクセストークンを取得します。
トークンリクエスト
取得した認可コードをトークンエンドポイントに送信します。
POST
https://******************.auth.ap-northeast-1.amazoncognito.com/oauth2/token?
code=8e0f29bd-d2**************&
grant_type=authorization_code&
client_id=5b2ngtb4*******************&
redirect_uri=http://localhost/callback&
scope=oauth-test-api/read
_code_
先ほど取得した認可コードを設定します。_grant_type_
認可コードグラントグラントでは"authorization_code"を設定します。
リソースへのアクセス
いよいよシーケンス図 12 「リソースへのアクセス」を行います。
以下のように Authorization ヘッダーにアクセストークンを設定した状態でリクエストを行います。
ステータスコードが 200 になっており、リクエストが成功したことが確認できました。
再度 Authorization ヘッダーを削除してみるとステータスコードが 401 になり、オーソライザーによってアクセスが拒否されたことが確認できました。
まとめ
OAuth2.0 の仕組みについて具体例を交えてご紹介しました。
今回は OAuth2.0 の代表的なグラントタイプである認可コードグラントの手順をトレースするためにリソースサーバーの設定なども行ったのですが、API Gateway のオーソライザーは他にも色々は方式があります。
API Gateway での REST API へのアクセスの制御と管理
認証認可は独特の概念や用語が多くてとっつきにくいですが、少しでもこれから取り組む方の足がかりになれば幸いです。
参考
雰囲気で OAuth2.0 を使っているエンジニアが OAuth2.0 を整理して、手を動かしながら学べる本[2023 年改訂版]