こんにちは。さくらインターネット株式会社でバックエンドAPIを開発している筒井と申します。 さくらインターネット - Qiita Advent Calendar 2024 12日目の記事は、 ローカル環境にOSSのユーザー認証・認可システム「Keycloak」でユーザー認証するDjangoアプリケーションを作る方法について紹介します。

Keycloak + Djangoの組み合わせは結構ニッチなので日本語の情報が少ないですが、社内向けシステムを構築する際に役立つ場合があるので、 この機会に知見として共有したいと思います。 また、Dockerを使った場合のちょっとした嵌りポイントと解決方法についても紹介します。

本記事の対象は、Djangoの基本的な知識があって、自分でDjangoアプリケーションを作ったことがある方です。 Keycloakは知らなくても問題ありませんが、OpenID Connect(OIDC)についての基本的な知識があると理解しやすいかもしれません。

前提知識

Keycloakとは

KeycloakはOSSのユーザー認証・認可システムです。 OpenID ConnectOAuth 2.0SAMLなどの認証プロトコルをサポートしています。

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_USERNAMEKC_BOOTSTRAP_ADMIN_PASSWORDで指定した値(どちらもadmin)でログインできます。

DjangoアプリとKeycloakを連携するために、KeycloakにClientを作成します。

なお、前述のサンプルアプリケーションでは予め作ったClientを起動時にKeycloakにインポートしているので、ここに書いている作業は不要です。

作成手順は以下の通りです。

Clientの作成1
Clientの作成1
Clientの作成2
Clientの作成2
Clientの作成3
Clientの作成3

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の作成4
Clientの作成4

Client IDとSecretは後述するdjango-allauthの設定で使います。Secretは以下のボタンからコピーできます。

Clientの作成5
Clientの作成5

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_idsecretには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アプリケーションにログインする際、以下の流れでリクエストが行われます。

  1. ユーザーからDjangoアプリケーションへのログインリクエスト
  2. DjangoアプリケーションからKeycloakへのリクエスト(settings.pyのSOCIALACCOUNT_PROVIDERSで設定したserver_urlの内容http://keycloak.private:8080/realms/master/.well-known/openid-configurationにリクエスト)
  3. KeycloakからDjangoアプリケーションへのレスポンス
  4. 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/hostskeycloak.privateを追加して、ホストマシンからはkeycloak.privateはローカルホストにアクセスするように設定します。

Dockerを使わず環境構築した場合は、1.4.まですべてlocalhostでアクセスできるのでこの設定は不要です。 また、本番環境ではKeycloakサーバーにドメインを割り当てて、どのネットワークからもアクセスできるようになっているはずなので、こちらも/etc/hostsに設定する必要はありません。

実際にサンプルアプリケーションを使ってみる

サンプルアプリケーションを使ってみます。

python manage.py runserverでDjangoアプリケーションを起動して、サインアップ画面http://localhost:8000/accounts/signup/にアクセスします。

サインアップ時に紐づけたKeycloakユーザーのメールアドレスが認証済みの場合と認証済みでない場合でユーザー登録の挙動が異なります。

メールアドレスが認証済みのKeycloakユーザー
メールアドレスが認証済みのKeycloakユーザー

まずは、Keycloakユーザーのメールアドレスが認証済みの場合:

そのままDjangoユーザーのメールアドレスとして登録されます。

次に、Keycloakユーザーのメールアドレスが認証済みではない場合:

メールアドレスの認証を促すメールが送信されます。 なお、サンプルアプリケーションでは、メール送信はMailpitというテスト用SMTPサーバーを使って、送信したメールをブラウザで確認できるようにしています(実際にはメールは送られず、ローカルだけで確認できます)。

最後に

以上で、ローカル環境にKeycloakでユーザー認証するDjangoアプリケーションを構築する方法について紹介しました。

Keycloak自体の運用に手間がかかるので、新規で導入する際には慎重に検討する必要がありますが、 すでにKeycloakを活用している場合は、社内システムのユーザー認証としては有効な手段だと思います。 どなたかの参考になれば幸いです!


  1. たとえば、KeycloakのClientにはlocalhostだけ登録して、Djangoアプリケーションには127.0.0.1でアクセスすると、KeycloakとDjangoアプリケーションが連携できません。localhost127.0.0.1は異なるURLとして扱われるためです。 ↩︎