django adminをkeycloakで認証してSSOする

published
2025-01-23

初めに

django adminをall-authのライブラリとkeycloakで認証し、SSOする動作確認を行った備忘録になります。

こちらの記事を参考にさせていただきました。

環境

  • docker 27.4.0
  • python 3,13.1
  • django 5.1.2
  • django-allauth 65.3.1
  • keyclaok 26.1.0

諸々準備

ディレクトリ

├── demo1
│   ├── Dockerfile
│   ├── requirements.txt
├── demo2
│   ├── Dockerfile
│   └── requirements.txt
└── docker-compose.yml

requirements.txt

Django==5.1.2
django-allauth==65.3.1
django-allauth[socialaccount]==65.3.1

dockerを用いて検証用環境を作成していきます。django, keycloakのdockerは以下を参考にしました。

また、コンテナ<->ホスト<->コンテナ間の通信を、host networkingの機能を用いてlocalhostで接続できるようにします。

Warning

Host networkは現時点でMacのDocker Desktopでは使用できませんのでご注意ください
詳細: https://docs.docker.com/engine/network/drivers/host/

dockerfile定義

FROM python:3
ENV PYTHONUNBUFFERED 1
RUN mkdir /code
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code/

docker-compose定義

version: "3.8"
services:
  demo1:
    build:
      context: ./demo1
    command: python3 manage.py runserver 0.0.0.0:8000
    volumes:
      - ./demo1:/code
    # host networkingを使用する
    network_mode: "host"
  demo2:
    build:
      context: ./demo2
    command: python3 manage.py runserver 0.0.0.0:8001
    volumes:
      - ./demo2:/code
    network_mode: "host"
  keycloak:
    image: quay.io/keycloak/keycloak:26.1.0
    container_name: keycloak
    command: start-dev
    environment:
      - KC_BOOTSTRAP_ADMIN_USERNAME=admin
      - KC_BOOTSTRAP_ADMIN_PASSWORD=admin
    volumes:
      - keycloak_data:/opt/keycloak/data
    network_mode: "host"
volumes:
  keycloak_data:

ビルド後、djangoの諸々のコマンドを実行します。

$ docker-compose run demo1 django-admin startproject config .
$ docker-compose run demo1 python manage.py startapp demo
$ docker-compose run demo2 django-admin startproject config .
$ docker-compose run demo2 python manage.py startapp demo

今回モデルは既存のUserを使うのでmigrateも済ませます

$ docker-compose run demo1 python manage.py migrate
$ docker-compose run demo2 python manage.py migrate

keycloakの設定

keycloak側の準備をします。行うこととしては各demo用のclientの作成と、認証に使用するUserの作成になります。

client生成

PRで使用するClient情報をClients->Create clientから作成します。
作成後、Valid redirect URIsにhttp://localhost:8000/accounts/oidc/keycloak/login/callback/を入力します。
keycloak-Client

demo2側も同様に作成します。

User作成

認証に使用するUserをUsers->Add userから作成します。
keycloak-User

作成後、Credencials->Set passwordからパスワード情報を作成します。
keycloak-user2

demoの設定

共通

以下all-authのドキュメントを参考にdemo1, demo2どちらにもall-authでkeycloakを使用できるように設定を行います。

# settings.py

INSTALLED_APPS = [
    ...
    'demo',
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "allauth.socialaccount.providers.openid_connect",
]

MIDDLEWARE = [
    ...
    "allauth.account.middleware.AccountMiddleware",
]

TEMPLATES = [
    {
        # demo1だけでok
        'DIRS': [
            BASE_DIR / "templates",
        ],
    },
]

AUTHENTICATION_BACKENDS = [
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
]

SOCIALACCOUNT_PROVIDERS = {
    "openid_connect": {
        "APPS": [
            {
                "provider_id": "keycloak",
                "name": "Keycloak",
                "client_id": "demo1",
                # 先ほど作成したClientのシークレット
                "secret": "sZgw6WF7i5Xr2a0sXq6HTyzhSKOcopPB",
                "settings": {
                    "server_url": "http://localhost:8080/realms/master/.well-known/openid-configuration",
                },
            }
        ]
    }
}
# あるとall-authのformでsocialのみになるので設定
ACCOUNT_EMAIL_VERIFICATION = 'none'
SOCIALACCOUNT_ONLY = True

# adapterをカスタマイズする
SOCIALACCOUNT_ADAPTER = 'demo.adapters.CustomSocialAccountAdapter'

# session名被るのでどちらかのみ変更
SESSION_COOKIE_NAME = 'demo2'

django-adminの認証もkeycloakを用いたいため、demoアプリ側のurlのinclude含め、config/urlsの設定を以下に変更します。

from django.contrib import admin
from django.urls import path, include
from allauth.account.decorators import secure_admin_login

admin.autodiscover()
admin.site.login = secure_admin_login(admin.site.login)

urlpatterns = [
    path("", include("demo.urls", namespace="demo")),
    path("accounts/", include("allauth.urls")),
    path('admin/', admin.site.urls),
]

keycloakで認証後のUser生成でis_superuser周りをTrueにしたいため、adapterを用いてカスタマイズします

# demo/adapters.py

from allauth.socialaccount.adapter import DefaultSocialAccountAdapter

class CustomSocialAccountAdapter(DefaultSocialAccountAdapter):
    def save_user(self, request, sociallogin, form=None):
        user = super().save_user(request, sociallogin, form)
        user.is_superuser = True
        user.is_staff = True
        user.save()
        return user

demo1側

demo1側はdjango templateでdemo1, demo2どちらのadminにも遷移できる画面を生成します。

# demo/View
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView

class DemoView(LoginRequiredMixin, TemplateView):
    template_name = "demo.html"

demo.htmlのテンプレートを作成します。

<html>
  <body>
      <a href="{% url 'admin:index' %}">Go to demo1 admin</a>
      <br />
      <a href="http://localhost:8001/go-to-admin/">Go to demo2 admin</a>
      <br />
  </body>
</html>

demo2側

keycloakのデフォルトだと、keycloakの認証画面の前にテンプレートを挟むので、そのままkeycloakの認証にリダイレクトするように設定します。

# demo/view

from django.views.generic.base import View
from allauth.socialaccount.providers.openid_connect.views import OpenIDConnectOAuth2Adapter

class GoToAdminView(View):
    def get(self, *args, **kwargs):
        return OpenIDConnectOAuth2Adapter(self.request, provider_id='openid_connect').get_provider().redirect(self.request, process='login', next_url='/admin')
# urls
from django.urls import path
from .views import GoToAdminView

app_name = "demo"

urlpatterns = [
    path("go-to-admin/", GoToAdminView.as_view(), name="go-to-admin")
]

確認

dockerを立ち上げて、http://localhost:8000にアクセスします。
keycloak-login

SignIn->Keycloak->Continueからkeycloakの認証画面に遷移します。
keycloak-sigin

作成したUser情報を入れて認証したのち、表示されているGo to demo1 adminでdemo1側のadminに遷移します。
keycloak-admin

http://localhost:8000 に戻りGo to demo2 adminをクリックし、demo2側に遷移しても、そのままdemo2のdjango adminが表示されます。

まとめ

all-authがとても良しなにOIDC認証周りを行ってくれるなと思いました。
テンプレート画面から複数のdjano adminのSSOをイメージできたので、今回はここまでにします。

コードはgithubに上げてあります。