はじめに
一度シリーズをまとめたが、優先度を下げていたKeycloak(認証基盤)にも触れてみることにした。他の要素との依存関係が複雑になりやすいという理由で後回しにしていたが、実際に触ってみたら、これまでのVaultなどとも繋がる発見が多かった。
こんな方におすすめ
- Keycloakのような認証基盤(SSO)が何をしているのか知りたい方
- アクセストークンとリフレッシュトークンの違いが気になる方
- 「同じパスワードで色々なサービスにログインできる」仕組みの裏側が気になる方
Keycloakを起動する
docker run -d --name keycloak -p 8082:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:latest start-dev
つまずいたポイント:Cloud Shellのプロキシとの相性
ブラウザでログイン画面までは開けたが、ログイン後の管理画面で「Failed to fetch」というエラーが出て、画面が真っ暗になった。開発者ツールで調べると、CORSポリシーによってブロックされていることが分かった。
Cloud Shellの「ウェブでプレビュー」は、専用の長いURL(https://8082-cs-xxxx...cloudshell.dev)を経由してアクセスする仕組みになっている。Keycloakはこの本当のアクセス元URLを認識できておらず、内部的な通信がうまく成立していなかった。
docker rm -f keycloak
docker run -d --name keycloak -p 8082:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
-e KC_HOSTNAME=https://(実際のCloud ShellプレビューURL) \
-e KC_PROXY_HEADERS=xforwarded \
-e KC_HTTP_ENABLED=true \
quay.io/keycloak/keycloak:latest start-dev
正しいホスト名とプロキシヘッダーの設定を追加したところ、ログイン画面までは進めた。ただ、その先の管理画面(重めのSPA画面)は、結局Cloud Shellのプロキシ構造と相性が悪く、正常に動かなかった。管理画面のUIを無理に動かすのは諦めて、KeycloakのREST APIを直接叩く方向に切り替えた。
APIで認証の流れを体験する
トークンを取得する(ログイン)
curl -s -X POST http://localhost:8082/realms/master/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin" \
-d "grant_type=password"
access_tokenという長い文字列が返ってきた。これが、ユーザー名・パスワードでの本人確認が済んだ後にもらえる「通行証」にあたる。
トークンを使って操作する
TOKEN=$(curl -s -X POST http://localhost:8082/realms/master/protocol/openid-connect/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "client_id=admin-cli" -d "username=admin" -d "password=admin" -d "grant_type=password" \
| grep -oP '"access_token":"\K[^"]+')
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8082/admin/realms/master
パスワードを再入力することなく、トークンだけでレルム(後述)の情報が取得できた。
トークンの有効期限を確認する
"accessTokenLifespan":60
有効期限60秒だった。試しに65秒待ってから同じトークンで再度アクセスすると、401 Unauthorizedで弾かれた。同じトークンの文字列自体は何も変わっていないのに、時間が経っただけで無効になる、というのを実際に確認できた。
そもそも、アクセストークンとは何なのか
一連の作業を通して、認証の仕組みについて理解を深めた内容を整理する。
OTP(ワンタイムパスワード)との違い
似た「短時間で無効になるもの」として、OTP(二段階認証などで使われる使い捨てのコード)がある。この2つは役割が違う。
- OTP:「あなたは本当に本人ですか?」を確認する、本人確認のための証拠。使ったらそこで終わり
- アクセストークン:本人確認が済んだ"後"に発行される、その後の通行証。一定時間、何度でも使い回せる
リフレッシュトークンによる自動更新
アクセストークンが短時間で切れても、ユーザーが毎回パスワードを入力しなくて済むよう、リフレッシュトークンという別の鍵を使って、裏側で自動的に新しいアクセストークンを取得し続ける仕組みがある。
ここで「リフレッシュトークンが盗まれたら危ないのでは」という疑問が湧いたが、調べてみると対策も用意されていた。
- リフレッシュトークン自体にも有効期限がある(今回の設定では最大10時間)
- 使うたびに、古いリフレッシュトークンを無効化して、新しいものに差し替える設定ができる(
revokeRefreshToken)。これにより、もし盗まれても、正規ユーザーが次に使った瞬間に、盗まれた側のトークンは無効になる
完全に防げるわけではない(差し替わる直前の一瞬を狙われる可能性は残る)が、被害が「気づかれないまま長時間続く」ことは防げる、という設計だった。
レルムという単位
Keycloakでは、ユーザーや権限設定が「レルム」という区画ごとに完全に分離されている。今回作ったmyappレルムは、masterレルムとは完全に独立した別の区画で、両者の間にユーザー情報のつながりは一切ない。これにより、片方(例えば一般利用者向けのmyapp)が突破されても、もう片方(管理者権限を持つmaster)には影響が及ばない、という設計になっている。
つまずいたポイント:一般ユーザーでログインできない
myappレルムに一般ユーザーtestuserを作成し、そのユーザーでログインを試すと、Account is not fully set upというエラーになった。ユーザー個別の設定、レルムのデフォルト設定を順に確認したが、原因が特定できなかった。
最終的に、メールアドレスなどのプロフィール情報を最初から含めてユーザーを作り直したところ(testuser2)、ログインに成功した。おそらく、ユーザープロフィールの検証が新しいバージョンで強化されており、基本情報が不足しているとログインを拒否される仕様になっていたと考えられるが、断定はできていない。
エラーメッセージだけでは分からなかった原因を、条件を変えて試すことで特定できた、という経験だった。
まとめ
今回、以下を体験できた。
- Keycloakを起動し、Cloud Shell特有のプロキシ問題に対処する
- APIを直接叩いて、認証(ログイン→トークン発行→トークンでの操作→失効)の一連の流れを体感する
- レルムという分離の仕組みと、その設計意図を理解する
- 原因不明のログインエラーに遭遇し、条件を変えて切り分けながら解決する
最後に、それぞれ何のためにやったのか、狙いを簡潔にまとめておく。
- 管理画面が動かないと分かった時点でAPI操作に切り替えたこと:UIの表示問題に時間をかけすぎず、本来の目的(認証の流れを理解すること)に立ち返るため
- わざとトークンを失効させて試したこと:仕組みの説明を読むだけでなく、実際に時間経過で無効になる様子を自分の目で確認するため
Vault、Consul、そして今回のKeycloak、どれも根っこには「完全に防ぐのではなく、被害を検知・限定し、早期に取り戻せる状態にしておく」という同じ設計思想が流れていた。
実は、今回やっていたのはSSOの「土台」だった
ここまでKeycloakを「SSO(シングルサインオン)を実現する認証基盤」として触ってきたが、実際にAPIで叩いていたのは、SSOそのものではなく、SSOを成り立たせている土台の部分だった。
改めて整理すると、SSOの本質は「1回のログインで、複数の別々のサービスに、それぞれ再ログインすることなくアクセスできる」ことにある。例えば、Googleアカウントに一度ログインすれば、Gmail・YouTube・カレンダーといった別々のサービスに、個別のパスワード入力なしでアクセスできる、というのがSSOの核心。実際のSSOログインでは、ブラウザが利用先のアプリと認証基盤の間を行き来し、さらにアプリのサーバー自身が裏側で認証基盤と直接通信する、という複数の経路を経て初めて成立する。
一方、今回体験した「アクセストークンを取得し、リフレッシュトークンで自動更新し続ける」という仕組みは、1つのアプリの中で、パスワードの再入力を省略し続けるための部品にすぎない。SSOがあってもなくても存在する、一般的なログインセッション維持の仕組みで、今回は1つのクライアント(admin-cli)でしか試していないため、「複数の別々のアプリが、同じログインを共有する」というSSOの核心部分は、実はまだ体験できていない。
階層として整理すると:
- 土台:ログイン→トークン発行→トークンでの操作→失効・更新(今回、実際に手を動かした部分)
- その上に成り立つ応用:1つのログインを、複数の別々のアプリが共有して使う=SSO本体(今回は未体験)
「SSOができている」ことを、複数システム間の疎通確認として使う運用は、この構造を踏まえると理にかなっている。SSOのログインフローが完走するということは、ブラウザ↔アプリ、ブラウザ↔認証基盤、そしてアプリのサーバー↔認証基盤という、複数の経路すべてが正常に機能している証拠になるからだ。今回はその土台部分だけを切り出して体験した、という位置づけになる。