こんにちは。さくらインターネット株式会社でバックエンドAPIを開発している筒井と申します。 さくらインターネット - Qiita Advent Calendar 2024 12日目の記事は、 ローカル環境にOSSのユーザー認証・認可システム「Keycloak」でユーザー認証するDjangoアプリケーションを作る方法について紹介します。
Keycloak + Djangoの組み合わせは結構ニッチなので日本語の情報が少ないですが、社内向けシステムを構築する際に役立つ場合があるので、 この機会に知見として共有したいと思います。 また、Dockerを使った場合のちょっとした嵌りポイントと解決方法についても紹介します。
本記事の対象は、Djangoの基本的な知識があって、自分でDjangoアプリケーションを作ったことがある方です。 Keycloakは知らなくても問題ありませんが、OpenID Connect(OIDC)についての基本的な知識があると理解しやすいかもしれません。
前提知識
Keycloakとは
KeycloakはOSSのユーザー認証・認可システムです。 OpenID Connect、OAuth 2.0、SAMLなどの認証プロトコルをサポートしています。
Keycloakに登録しておいたユーザー情報を使って、他のアプリケーションでユーザー認証を行えます。 複数のアプリケーションのユーザーを一元管理する際に便利です。
django-allauthとは
django-allauthはDjangoのユーザー認証、登録、アカウント管理機能を強化するDjango拡張です。 ソーシャルログイン、SAML、OpenID Connectなど、様々な認証プロバイダーに対応しています。 本記事では、前述したKeycloakとDjangoアプリケーションを連携させるために使います。
環境構築
使用するPython、各種ライブラリ、ツールのバージョンは以下の通りです。
- Python 3.12
- Django 4.2.17
- django-allauth 65.3.0
- Docker Desktop 4.36.0
- Keycloak 26.0
以下のGitHubリポジトリに私が作ったサンプルアプリケーションがあります。
ryu22e/allauth_keycloak_example
上記のサンプルアプリケーションはVisual Studio Code + Dev Containersプラグインを使ってDockerコンテナ上に環境を構築できるようになっています。 Visual Studio Codeでアプリケーションを開いた後、画面右下の「Reopen in Container」をクリックすると、Dockerコンテナ上で開発環境が起動します。
以下では、上記のサンプルアプリケーションの構成について説明します。
Docker Composeの設定ファイル
Docker Composeの設定ファイルは以下の通りです。
services:
# Djangoアプリ
app:
container_name: allauth_keycloak_example
image: mcr.microsoft.com/devcontainers/python:1-3.12-bullseye
working_dir: /workspace
tty: true
volumes:
- .:/workspace:cached
depends_on:
- keycloak
- mailpit
# Keycloakサーバー
keycloak:
container_name: keycloak
image: quay.io/keycloak/keycloak:26.0
ports:
- 8080:8080
command: ["start-dev", "--import-realm"]
volumes:
- keycloak-volume:/opt/keycloak/data
- ${LOCAL_WORKSPACE_FOLDER:-./keycloak}:/opt/keycloak/data/import:ro
restart: always
networks:
default:
aliases:
- keycloak.private # この設定が必要
environment:
# adminのユーザー名とパスワード
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
- KC_BOOTSTRAP_ADMIN_PASSWORD=admin
# 開発用の疑似SMTPサーバー
mailpit:
container_name: mailpit
image: axllent/mailpit:v1.21
ports:
- 1025:1025
- 8025:8025
volumes:
- mailpit-volume:/data
networks:
default:
aliases:
- mailpit.private
restart: always
environment:
- MP_DATA_FILE=/data/mailpit.db
volumes:
keycloak-volume:
mailpit-volume:
keycloakのnetworksにkeycloak.private
というエイリアスを設定していますが、これが必要な理由は後述します。
Keycloak側の設定
http://localhost:8080
を開いてKeycloakの管理画面にアクセスします。
ユーザー名、パスワードはcompose.ymlの環境変数KC_BOOTSTRAP_ADMIN_USERNAME
、KC_BOOTSTRAP_ADMIN_PASSWORD
で指定した値(どちらもadmin
)でログインできます。
DjangoアプリとKeycloakを連携するために、KeycloakにClientを作成します。
なお、前述のサンプルアプリケーションでは予め作ったClientを起動時にKeycloakにインポートしているので、ここに書いている作業は不要です。
作成手順は以下の通りです。
Keycloakにログインした後にブラウザがDjangoアプリにリダイレクトしますが、そのとき有効なURLとして、Valid redirect URIsにhttp://localhost:8000/accounts/oidc/keycloak/login/callback/
とhttp://127.0.0.1:8000/accounts/oidc/keycloak/login/callback/
を追加します。
どちらか1つでもいいですが、Djangoアプリにアクセスする際、localhost
でも127.0.0.1
でもKeycloakと連携できたほうが便利なので、両方追加しています1。
Client IDとSecretは後述するdjango-allauthの設定で使います。Secretは以下のボタンからコピーできます。
django-allauthの導入方法
まず、django-allauthをインストールします。
$ pip install django-allauth[socialaccount]
settings.py
に以下の設定を追加して、django-allauthを有効にします。
"""settings.py"""
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
# 以下の設定を追加:
"django.template.context_processors.request",
],
},
},
]
AUTHENTICATION_BACKENDS = [
...
# userrnameでログインする場合は必須
"django.contrib.auth.backends.ModelBackend",
# 以下の設定を追加:
"allauth.account.auth_backends.AuthenticationBackend",
...
]
INSTALLED_APPS = [
...
# 以下のアプリケーションは必須:
"django.contrib.auth",
"django.contrib.messages",
# 以下のアプリケーションを追加:
"allauth",
"allauth.account",
"allauth.socialaccount",
"allauth.socialaccount.providers.openid_connect",
]
MIDDLEWARE = (
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
# 以下のミドルウェアを追加:
"allauth.account.middleware.AccountMiddleware",
)
続いて、Keycloakと連携するための設定を追加します。 かつてdjango-allauthにはKeycloak専用の認証プロバイダーがありましたが、0.56.0以降は廃止され、代わりにOpenID Connectプロバイダーを使うようになりました。
設定方法は以下の公式ドキュメントに記載されています。
https://docs.allauth.org/en/dev/socialaccount/providers/keycloak.html
上記のドキュメントを参考に、settings.py
に以下の設定を追加します。
client_id
とsecret
にはKeycloakのClient作成時に取得したClient ID、Client Secretを入れます。
実際の開発では、本番環境で値を入れ替えられるように環境変数で設定することをおすすめします。
"""settings.py"""
SOCIALACCOUNT_PROVIDERS = {
"openid_connect": {
"APPS": [
{
"provider_id": "keycloak",
"name": "Keycloak",
# client_idとsecretは後述する「Keycloak側の設定」で取得した値を入れる
"client_id": "<insert-id>",
"secret": "<insert-secret>",
"settings": {
"server_url": "http://keycloak.private:8080/realms/master/.well-known/openid-configuration",
},
}
]
}
}
urls.py
は以下のように設定します。
"""urls.py"""
from django.contrib import admin
from django.urls import path, include
from allauth.account.decorators import secure_admin_login
# adminへのログインをKeycloakで認証する場合は以下の設定を追加
admin.autodiscover()
admin.site.login = secure_admin_login(admin.site.login)
urlpatterns = [
path("accounts/", include("allauth.urls")), # これは必須
path("admin/", admin.site.urls),
]
サンプルアプリケーションでは上記の設定以外にトップページを作成していますが、説明は省略します。
ホストマシン側の設定
ホストマシンの/etc/hosts
に以下の設定を追加します。
127.0.0.1 keycloak.private
::1 keycloak.private
Dockerで開発環境を構築する際は、上記の設定が必要です。 以下で理由を説明します。
ユーザーがDockerコンテナ上に構築されたDjangoアプリケーションにログインする際、以下の流れでリクエストが行われます。
- ユーザーからDjangoアプリケーションへのログインリクエスト
- DjangoアプリケーションからKeycloakへのリクエスト(settings.pyの
SOCIALACCOUNT_PROVIDERS
で設定したserver_url
の内容http://keycloak.private:8080/realms/master/.well-known/openid-configuration
にリクエスト) - KeycloakからDjangoアプリケーションへのレスポンス
- Djangoアプリケーションからユーザーへのレスポンス(Keycloakのログインページ
http://keycloak.private:8080/realms/master/protocol/openid-connect/auth?client_id=testclient&redirect_uri=****
にリダイレクト)
このとき、2.
はコンテナ同士の通信、4.
はホストマシンとコンテナ間の通信を行います。
コンテナ同士の通信はconpose.ymlに設定したネットワークエイリアスkeycloak.private
で通信できますが、ホストマシンとコンテナ間では別のネットワークを使うため、keycloak.private
では通信できません。keycloak.private
というドメインは存在しないからです。
4.
のリダイレクト先だけlocalhost
にできればいいのですが、django-allauthではこれはできません。
そこで、/etc/hosts
にkeycloak.private
を追加して、ホストマシンからはkeycloak.private
はローカルホストにアクセスするように設定します。
Dockerを使わず環境構築した場合は、1.
〜4.
まですべてlocalhost
でアクセスできるのでこの設定は不要です。
また、本番環境ではKeycloakサーバーにドメインを割り当てて、どのネットワークからもアクセスできるようになっているはずなので、こちらも/etc/hosts
に設定する必要はありません。
実際にサンプルアプリケーションを使ってみる
サンプルアプリケーションを使ってみます。
python manage.py runserver
でDjangoアプリケーションを起動して、サインアップ画面http://localhost:8000/accounts/signup/
にアクセスします。
サインアップ時に紐づけたKeycloakユーザーのメールアドレスが認証済みの場合と認証済みでない場合でユーザー登録の挙動が異なります。
まずは、Keycloakユーザーのメールアドレスが認証済みの場合:
そのままDjangoユーザーのメールアドレスとして登録されます。
次に、Keycloakユーザーのメールアドレスが認証済みではない場合:
メールアドレスの認証を促すメールが送信されます。 なお、サンプルアプリケーションでは、メール送信はMailpitというテスト用SMTPサーバーを使って、送信したメールをブラウザで確認できるようにしています(実際にはメールは送られず、ローカルだけで確認できます)。
最後に
以上で、ローカル環境にKeycloakでユーザー認証するDjangoアプリケーションを構築する方法について紹介しました。
Keycloak自体の運用に手間がかかるので、新規で導入する際には慎重に検討する必要がありますが、 すでにKeycloakを活用している場合は、社内システムのユーザー認証としては有効な手段だと思います。 どなたかの参考になれば幸いです!
たとえば、KeycloakのClientには
localhost
だけ登録して、Djangoアプリケーションには127.0.0.1
でアクセスすると、KeycloakとDjangoアプリケーションが連携できません。localhost
と127.0.0.1
は異なるURLとして扱われるためです。 ↩︎