はじめに
本日は、以下のようなシーケンスで OpenID Connect の Authorization Code Flow を確認していこうと思います。
開発済みのプロジェクトのいくつかで Keycloak,OpenID Connect, Spring Cloud Gateway が使われており順に理解していく必要を感じたため、今回は基本的な構成から学んでいきたいと思います。
UserInfo Endpoint(Spring Boot) および IdP(docker container として起動) のサンプルコードは以下のリポジトリに用意しました!
https://github.com/kiyotake-tagbangers/resource-server-for-blog
そもそも、 OpenID Connect とは
OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol.
と OpenID Connect(以降 OIDC) のウェブサイト にあるように、認証情報を含むアイデンティティ情報を受け渡しできるように、OAuth 2.0(以降 OAuth) を拡張したものです。OAuth は権限委譲(=認可)のためのフレームワークであり、OAuth をそのまま認証に利用すると脆弱性があります。
参考: 単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる
具体的には、悪意のあるクライアントがアクセストークンを取得してしまうと、別の OAuth を利用して認証しているリソースサーバにアクセスできてしまうことが問題のようです。
OIDC では、 OAuth のアクセストークンに加えてJWT形式の ID Token を発行し、こちらにユーザを識別できる情報を入れることで攻撃を防ぎます。(フローの6番の箇所)
OAuth のアクセストークンを発行するのが認可サーバ(Authorization Server)、OIDC の ID Token を発行するのが OpenID Provider(IdP) なのですが、
OIDC が OAuth の拡張であるという関係上、IdP が Authorization Server を兼ねるケースが多くなりますし、今回だとまさに Keycloak がどちらも兼ねています。
ハンズオン
では、上記のシーケンスを実際に確認していこうと思います。リポジトリの README にもコマンドラインベースでの手順を記載しましたが、ブログではキャプチャをふんだんに入れてみます!
まずは、UserInfo Endpoint を起動します。(コマンドラインかIDEで)
$ export JAVA_HOME=`/usr/libexec/java_home -v 11` $ java -version openjdk version "11.0.2" 2019-01-15 $ ./mvnw clean package $ ./mvnw spring-boot:run
次に、IdP を起動します。
$ docker-compose up -d
を実行したら、.keycloak ディレクトリ以下にある sample-realm.json が読み込まれて、sample realm および realm 内で設定する client, user が作成済みの状態で keyclaok が起動します。
realm とは独立してユーザ、クレデンシャル、ロールなどの設定を管理できる領域です。複数の realm を作った場合、それらは互いに分離されて管理や認証ができます。
http://localhost:8000/ にアクセスし、 Administration Console(Master realm) にログインします(Username,Password は dokcer-compose に記載の keycloak)
sample realm に sampel client が作成されているので、 Secret の情報をコピーしておきます。
では、Authorization Endpoint へ 認可コード(Authorization code)を取得するためのリクエストを Postman で作成します。
http://localhost:8000/auth/realms/sample/protocol/openid-connect/auth? をURL入力欄に記載し、Params に Key-Value を追加していきましょう。
- response_type に code を設定することで、Authorization Server は認可コードの発行を求められていることを知る
- state はクライアントが生成したランダムな値を設定する
- ユーザのセッションと紐づけて管理することでクロスサイトフォージェリを防ぐために使用
- redirect_url は sample realm を登録した際に指定したもの
- OIDCでは scope に openid の指定が必須
完成したリクエストをコピーして... ブラウザからリクエストを送信すると sample realm のログイン画面が表示されます。
ローカルの検証用なので、Username: sample , Password: 1qazxsw2 でログインできるように設定しています。
ログインに成功すると、sample realm に設定したリダイレクトエンドポイントにリダイレクトされ 認可コードが返されます(フローの4番の箇所)。
code をコピーして次のステップに進みます。
Token Endpoint に対して、認可コードを使い Access Token などを取得するためのリクエストを行います。
Postman で以下のようにリクエストを作成します。
- grant_type に authorization_code を設定することで、Authorization Code Flow によるリクエストであると分かる
- code に認可コードを指定
- client_secret に sample realm の secret をペースト
Postman からリクエストを送信すると、以下のようなトークンレスポンスが返ってきます。(フローの6番の箇所)
アクセストークン、リフレッシュトークン(アクセストークンの再発行に利用する)、有効期限 、と OIDC なので ID Token を取得できました。こちらのアクセストークンをコピーしておきます。
Header に Bearer トークンであるアクセストークンの情報を入れてリクエストしてみましょう!UserInfo Endpoint に認証が通ると200番のHTTPレスポンスステータスコードとリソースが返されます!
Access Token の中身を見てみる
Keycloak はアクセストークンを JWT(JSON Web Token) 形式で提供しています。
jwt.io にて手軽にJWTを Decoded して中身が確認できるようなので、アクセストークンをみてみました。
- iat(Issued AT) はJWTの発行時間
- iss(ISSuser) は ID Token の発行者を表します
- aud(AUDience) は ID Token の発行を受ける Relying_Party のクライアントIDが入ります
- sub(SUBject) はエンドユーザの識別子
- scope は OIDC では仕様で規定されており、profile を指定しているため、プロフィール情報へのアクセスが可能です
以上です。実際に動かして挙動を確認することで Authorization Code Flow についてのイメージが少しだけつかめました。
API Gateway pattern と組み合わせて、横断的関心事である認証認可を Spring Cloud Gateway に任せて、下流に Resource Server(UserInfo Endpoint) を複数置いてみる、なども試してみると面白そうです。
参考
https://openid.net/specs/openid-connect-core-1_0.html#Overview
https://openid.net/connect/faq/
https://authya.booth.pm/items/1550861
https://authya.booth.pm/items/1296585
https://www.keycloak.org/docs/latest/getting_started/
https://qiita.com/TakahikoKawasaki/items/498ca08bbfcc341691fe#_reference-9686d2988451466463eb
https://qiita.com/TakahikoKawasaki/items/4ee9b55db9f7ef352b47#1-response_typecode
https://www.udemy.com/share/103Aq4/