LoginSignup
20
6

More than 5 years have passed since last update.

RFC7636(PKCE:Proof Key for Code Exchange by OAuth Public Clients)対応を試してみる

Last updated at Posted at 2017-12-19

はじめに

日立製作所の@tnorimatと申します。面白い企画に相乗りさせて頂きます。Keycloakのパッチ開発の注意点と、開発したパッチであるRFC7636対応について紹介します。

まず、今回対象となるRFC7636(通称PKCE:ぴくしー)は、OAuthの認可コード横取り攻撃対策のための規格です。PKCEそのものについての説明は、他によい記事がありますので、ここでは割愛します。例えば、下記の記事からたどれる一連の記事を参考にするとよいでしょう。

@TakahikoKawasaki著 PKCE: 認可コード横取り攻撃対策のために OAuth サーバーとクライアントが実装すべきこと

KeycloakへのPKCE対応パッチ開発について

KeycloakがPKCEに対応していないことに気づき、筆者が認可サーバーにおける実装パッチを提案し、バージョン3.1.0でマージされました。

パッチのマージにあたり、初めてだったので色々と苦労しましたので、気づいたことを記します。今後Keycloakへパッチ開発される方は参考にして頂けると幸いです。

  • developerメーリングリストは埋もれる

Keycloakのオフィシャルページには、パッチ開発にあたっては、まずはdeveloperメーリングリストで議論するようにありましたが、容易に埋もれます。

  • JIRAとPull Requestが優先

Keycloakでは、課題管理にはJBossのJIRA、ソース管理にはGithubが使われています。これらを通じて議論するのが埋もれずに済むコツです。作りたい機能がある場合は、まずは、これらを検索し、関連トピックがあればそちらで議論、なければ作成するとよいでしょう。

  • テストを書く

パッチを作ったら、Githubのリポジトリにpull requestを出すことで提出するのですが、この際、テストを書く必要があることに注意してください。

パッチの内容によっては、テストコードはArquillianという統合テスト用フレームワークを使う必要があり、慣れないうちは骨が折れます。テストが必要なことから、あまり大きな機能にしてしまうと、レビューで根本的な指摘があると大変です。そのため、なるべく小さな機能に区切ってパッチを出すとよいでしょう。

Arquillianは、Javaベースのプログラムの統合テスト用フレームワークです。初めて使用される方は、以下のサイトを参照することをお勧めします。

Arquillian Guides - Getting Started

PKCEの動作確認してみよう

さて、ここからは、今回開発した機能であるPKCE対応の動作を確認してみます。Keycloakに同梱のサンプルプログラムをクライアントとして使用し、APサーバーのログとパケットキャプチャで、RFC 7636がどのように動いているかを確認します。

詳細

使用するソフトウェアコンポーネントを以下に示します。

  • Keycloakのバージョン: 3.4.0.Final(2017/11/20時点での最新バージョン)

  • クライアント: oauth-client-exampleプロジェクト

前述のパッチは認可サーバー側の実装であるため、今回はクライアント側の処理を上述のプロジェクトのアプリケーションに実装し、使用しています。

  • リソースサーバー: database-serviceプロジェクト

クライアント、リソースサーバー双方とも、認可サーバーであるKeycloakと同じAPサーバー上にdeployします。

Keycloak 3.4.0.Finalの入手

Keycloak 3.4.0.Finalの認可サーバー本体、wildfly用クライアントアダプター(OpenID Connect)、そしてソースコードを入手します。

認可サーバー本体は、以下のサイトから入手できます。

Wildfly用クライアントアダプター(OpenID Connect)は以下のサイトから入手できます。

ソースコードは、以下のサイトから入手できます。

Keycloak 3.4.0.Finalのインストール

認可サーバーのインストール方法は、以下のサイトに記載されています。

今回は、認可サーバーと同じAPサーバー(wildfly)に、クライアントアプリケーションをdeployします。wildfly用クライアントアダプター(OpenID Connect)のインストール方法は、以下のサイトに記載されています。

デモ用の環境の設定

今回の試行のために、Keycloakに添付されている、demo-templateというデモ用環境を設定します。その方法は、以下のサイトに記載されています。

PKCE対応しているクライアントアプリケーションの準備

前述の通り、前述のパッチは認可サーバー側の実装です。クライアント側については、クライアントアダプターがPKCE未対応であるため、記事執筆時点では自作が必要です。今回は、筆者がJavaサーブレット用の実装としてKeycloakコミュニティに提案中の下記パッチを適用することで、PKCE対応クライアントWebアプリを作成します。

お手持ちのKeycloakのソースコードに、上記サイトのパッチをマージし、mvn clean install でビルドします。

ビルドで生成されたwarファイル(/examples/demo-template/third-party/target/oauth-client.war)を、APサーバーであるWildflyにdeployして下さい。deployする方法は、以下のサイトに記載されています。

結果

  • APサーバー(wildfly)のログと、キャプチャしたパケットの内容から、RFC 7636 PKCEのプロトコル処理がどのように行われているかを示します。

  • ここでは、PKCEのプロトコル処理が成功している例を示します。

  • 認可サーバー(Keycloak)、クライアントアプリケーション、リソースサーバーは、全て同じAPサーバー上にdeployされています。その為これら3者のログは、全て同じログファイルに出力されています。

クライアントアプリがAuthorization Code Flowを開始

  • APサーバーのログ

2017-11-20 10:29:17,356 DEBUG org.keycloak.servlet.ServletOAuthClient Generated codeVerifier = zns5R5Xmak7CXzifpPKjZalyYn-gBfbYGyqgZbSgheg

2017-11-20 10:29:17,356 DEBUG org.keycloak.servlet.ServletOAuthClient Encode codeChallenge = IWLIhv1NvG0tOvJe22Ke0c1Am02rUKS0LMTS0pgY3XE, codeChallengeMethod = S256

  • キャプチャしたパケット

GET /auth/realms/demo/protocol/openid-connect/auth?response_type=code&client_id=third-party&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth-client%2Fpull_data.jsp&state=0%2Fa437c3b3-b696-46c3-8c0f-ce8aae719616&scope=openid&code_challenge=IWLIhv1NvG0tOvJe22Ke0c1Am02rUKS0LMTS0pgY3XE&code_challenge_method=S256 HTTP/1.1

RFC 7636の仕様通りに、パラメータ(code_challengecode_challenge_method)が指定されています。

エンドユーザーが、認可サーバーにてログイン

  • キャプチャしたパケット

HTTP/1.1 302 Found

Location: http://localhost:8080/oauth-client/pull_data.jsp?state=0%2Fa437c3b3-b696-46c3-8c0f-ce8aae719616&code=uss.-FniBUXUy4f4X8YixU2B6bqV1nuwz407h6O9-5Qj3yc.dc289513-eaf6-48b7-8fd8-6539f54cc29b.939f0088-18ad-4a47-912e-b4fcf7076825

RFC 6749の仕様通り、認可コード(code)が払い出されています。認可サーバーがエンドユーザーのブラウザをリダイレクトさせることで、クライアントにこの認可コードを渡します。

クライアントアプリが、認可サーバーへ、トークン取得要求

  • APサーバーのログ

2017-11-20 10:29:24,022 DEBUG org.keycloak.servlet.ServletOAuthClient Before sending Token Request, codeVerifier = zns5R5Xmak7CXzifpPKjZalyYn-gBfbYGyqgZbSgheg

  • キャプチャしたパケット

POST /auth/realms/demo/protocol/openid-connect/token HTTP/1.1

grant_type=authorization_code&code=uss.-FniBUXUy4f4X8YixU2B6bqV1nuwz407h6O9-5Qj3yc.dc289513-eaf6-48b7-8fd8-6539f54cc29b.939f0088-18ad-4a47-912e-b4fcf7076825&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth-client%2Fpull_data.jsp&code_verifier=zns5R5Xmak7CXzifpPKjZalyYn-gBfbYGyqgZbSgheg

RFC 7636の仕様通り、クライアントはパラメータ(code_verifier)を送信しています。

認可サーバーが、トークン要求の検証を実施

  • APサーバーのログ

2017-11-20 10:29:24,096 DEBUG org.keycloak.protocol.oidc.endpoints.TokenEndpoint PKCE supporting Client, codeVerifier = zns5R5Xmak7CXzifpPKjZalyYn-gBfbYGyqgZbSgheg

2017-11-20 10:29:24,097 DEBUG org.keycloak.protocol.oidc.endpoints.TokenEndpoint PKCE codeChallengeMethod = S256

2017-11-20 10:29:24,097 DEBUG org.keycloak.protocol.oidc.endpoints.TokenEndpoint PKCE verification success. codeVerifierEncoded = IWLIhv1NvG0tOvJe22Ke0c1Am02rUKS0LMTS0pgY3XE, codeChallenge = IWLIhv1NvG0tOvJe22Ke0c1Am02rUKS0LMTS0pgY3XE

認可サーバーが、クライアントアプリへ、トークンを払出

  • APサーバーのログ

2017-11-20 10:29:24,119 DEBUG org.keycloak.events type=CODE_TO_TOKEN, realmId=448061c7-47ec-48f1-a7f2-1220825fff8f, clientId=third-party, userId=b2d04404-83b4-40ed-bc4d-0918c3a281fe, ipAddress=127.0.0.1, token_id=217e68ea-749d-4443-a995-52efa6a84bc3, grant_type=authorization_code, refresh_token_type=Refresh, refresh_token_id=2aa2783e-82cc-46e2-b6b2-ae3442371d31, code_id=dc289513-eaf6-48b7-8fd8-6539f54cc29b, client_auth_method=client-secret

  • キャプチャしたパケット

HTTP/1.1 200 OK

{"access_token":長いため割愛}

認可サーバー(Keycloak)から、クライアントに各種トークンが払い出されました。

次に、クライアントをちょっと改造し、わざとCode Verifierをトークン要求時に送信しないようにした場合の結果を示します。

クライアントアプリがAuthorization Code Flowを開始

  • キャプチャしたパケット

GET /auth/realms/demo/protocol/openid-connect/auth?response_type=code&client_id=third-party&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth-client%2Fpull_data.jsp&state=0%2F1603116b-bf49-40ae-9913-fdaaa3cf9c58&scope=openid&code_challenge=Hgl_oUpGOBqRXphp2-4gh-OTbpgJOnWn465e1I1k4zw&code_challenge_method=S256 HTTP/1.1

RFC 7636の仕様通りに、パラメータ(code_challenge, code_challenge_method)が指定されています。

エンドユーザーが、認可サーバーにてログイン

  • キャプチャしたパケット

HTTP/1.1 302 Found

Location: http://localhost:8080/oauth-client/pull_data.jsp?state=0%2F1603116b-bf49-40ae-9913-fdaaa3cf9c58&code=uss.8_NpulL3CLouKCoQf4588IXLjWzhZTOavZHXgayAngE.a53e4e6b-06de-4df5-b302-719e7e99d604.939f0088-18ad-4a47-912e-b4fcf7076825

RFC 6749の仕様通り、認可コード(code)が払い出されています。認可サーバーはエンドユーザーのブラウザをリダイレクトさせることで、クライアントにこの認可コードを渡します。

クライアントアプリが、認可サーバーへ、トークン取得要求

  • APサーバーのログ

    2017-11-20 11:22:28,689 DEBUG org.keycloak.servlet.ServletOAuthClient Before sending Token Request, drop codeVerifier intentionally = gy-Ho6M8UyVw98mNpASzpQ1XeRP51A07iIgb3eDfejU

  • キャプチャしたパケット

POST /auth/realms/demo/protocol/openid-connect/token HTTP/1.1 grant_type=authorization_code&code=uss.8_NpulL3CLouKCoQf4588IXLjWzhZTOavZHXgayAngE.a53e4e6b-06de-4df5-b302-719e7e99d604.939f0088-18ad-4a47-912e-b4fcf7076825&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth-client%2Fpull_data.jsp

RFC 7636の仕様に反して、わざとパラメータ(code_verifier)を送信していません。

認可サーバーが、トークン要求の検証を実施

  • APサーバーのログ

2017-11-20 11:22:28,758 WARN org.keycloak.protocol.oidc.endpoints.TokenEndpoint PKCE code verifier not specified, authUserId = b2d04404-83b4-40ed-bc4d-0918c3a281fe, authUsername = stian

検証で失敗しています。認可コード要求時に、PKCEのプロトコル処理のCode Challengeを送ったのち、トークン要求時、Code Verifierを送っていないために、エラーとなりました。

認可サーバーが、クライアントアプリへ、エラー応答を返却

  • APサーバーのログ

2017-11-20 11:22:28,760 WARN org.keycloak.events type=CODE_TO_TOKEN_ERROR, realmId=448061c7-47ec-48f1-a7f2-1220825fff8f, clientId=third-party, userId=b2d04404-83b4-40ed-bc4d-0918c3a281fe, ipAddress=127.0.0.1, error=code_verifier_missing, grant_type=authorization_code, code_id=a53e4e6b-06de-4df5-b302-719e7e99d604, client_auth_method=client-secret

  • キャプチャしたパケット

HTTP/1.1 400 Bad Request

{"error":"invalid_grant","error_description":"PKCE code verifier not specified"}

RFC 7636の仕様にあるとおり、認可サーバーから、クライアントへエラー応答が返って来ました。

おまけ

  • PKCEを実装していないクライアントの扱い

現時点(3.4.0.Final)では、KeycloakはPKCEのプロトコルに準拠していないクライアントが認可コード要求を出してきた場合、つまり、code challengeとcode challenge methodパラメータを付与せず認可コード要求を出してきた場合、通常通り認可コードフローを実行します。

  • PKCEのテストケース

KeycloakのPKCE機能について、Arquillianでの統合テスト用のテストケースのクラス(org.keycloak.testsuite.oauth.OAuthProofKeyForCodeExchangeTest)があります。

これを実行(テスト)し、その際出力されるログや、キャプチャされたパケットから、PKCEのプロトコル処理の様子を見ることができます。

20
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
6