世の中、覚えなければ上手く行かないことが多すぎる。だから、全てを覚えてから始めるんじゃなくて、やってみて失敗してから掘り下げるアプローチが好きです。まどかママの「上手な転び方を覚え」るってのは至言だと思う。
そんな訳で、Keycloakで認証を行うWebアプリを開発していて、動作確認も終わり、いざ検証環境で動かそうとしたら、Webアプリの起動が失敗してしまった。
-
Keycloakサーバは、検証環境でdocker-composeを使って構築。
-
Webアプリは、バックエンドをSpring Boot、フロントエンドをAngularで開発。
- Angularアプリではkeycloak-angularを使用。
- Spring Bootアプリはリソースサーバとして動作するのでspring-boot-starter-oauth2-resource-serverを使い、Angularアプリから送られてきたトークンを検証。
-
開発中は、検証環境のKeycloakサーバに接続して動作を確認。
-
Webアプリをコンテナにして、検証環境のKeycloakサーバと同じホストのDocker上で起動すると、Spring BootアプリからKeycloakサーバへの経路がない(no route to host)とのエラーで終了していた。
まず試したこと
そこでKeycloakコンテナとWebアプリコンテナを同じDockerネットワークに参加させ、Spring BootアプリからDockerネットワーク経由で接続するようにしてみた。
-
docker network create ${ネットワーク名}
でネットワークを作成。 - KeycloakとWebアプリのdocker-compose.ymlで、上記のネットワークに参加するように設定。
version: '3'
services:
app:
build:
context: .
container_name: app
ports:
- 50010:8080
networks:
- ${ネットワーク名}
networks:
${ネットワーク名}:
external: true
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://${keycloakコンテナ名}.${ネットワーク名}:8080/auth/realms/${レルム名}
これでSpring Bootアプリは、Keycloakサーバと通信が成功して、正常に起動するようにはなった。
が、Angularアプリでログインを行うと、keycloakサーバへのログインに成功するものの、その後で__AngularアプリからSpring BootアプリへのAjax通信で 401 Unauthorizedとなってしまった。__
ブラウザでレスポンスを見てみると、ヘッダに以下の情報があった。
WWW-Authenticate: Bearer error="invalid_token",
error_description="This iss claim is not equal to the configured issuer",
error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
どうやらAngularアプリとSpring BootアプリがアクセスするKeycloakサーバのURLは一致していないとのこと。
なので、どうしてもDockerコンテナ内部から外部ネット経由でアクセスする必要だった。
根本の原因
そこでネットから情報を漁ると、Dockerがiptablesを設定していて、それによって通信ができないらしい。
こちらの記事とか参考にしてiptablesの見方を勉強してみた。
まだ完全に理解できていないが、自ホストからパケットを送信する場合のチェインを辿ってみると、、、
# iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 12 packets, 853 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
※あとは割愛
Dockerkコンテナにデフォルトで割り当てられるbridgeネットワークのアドレス172.17.0.0/16からの通信は、docker0ブリッジには戻れない設定だと思えた。
回避策
現状の理解ではiptablesを弄るのもハードルが高いので、WebアプリのDockerコンテナがデフォルトのbridgeドライバを使ったネットワークではなく、hostドライバを使うように指定した。
version: '3'
services:
app:
build:
context: .
container_name: app
network_mode: host # 追加
# ポートマッピングは使えなくなるので削除
# ports:
# - 50010:8080
これによりコンテナ内部で使うポートが、直接ホストで使われてしまうが、そこはSpring Bootの設定ファイルで簡単にポート番号を変更できるので良しとしました。