# Python Flask/Django 연동

Keycloak은 OpenID Connect와 SAML 기반의 인증·권한 부여 관리 플랫폼이다. Python 환경에서도 Flask, Django 같은 대표적인 웹 프레임워크와 연동하여 인증을 처리할 수 있다. Keycloak에서 제공하는 OIDC 방식을 사용하면, 별도의 인증 로직 없이도 토큰 검증을 통한 안전한 접근 제어를 구현할 수 있다. 다음에서는 Keycloak에서 클라이언트를 설정하고 Flask와 Django 애플리케이션을 연동하는 과정을 설명한다.

### Keycloak 클라이언트 설정

1. **Realm 생성** Keycloak에 접속 후 새로운 Realm을 생성한다. 이 Realm은 Python 웹 애플리케이션들이 참조할 인증 공간이다.
2. **클라이언트(Client) 생성**
   * Keycloak Admin Console에서 ‘Clients’ 메뉴를 연다.
   * ‘Create client’를 선택하고, 클라이언트 ID를 입력한다. 예: `python-flask-client`
   * ‘OpenID Connect’를 선택한다.
   * ‘Confidential’ 또는 ‘Public’ 유형을 선택한다.
     * 백엔드 서버가 토큰을 안전하게 관리할 수 있는 구조라면 일반적으로 ‘Confidential’을 사용한다.
   * ‘Root URL’ 혹은 ‘Valid Redirect URIs’에 Flask 또는 Django 애플리케이션의 리다이렉트 URI를 정확히 등록한다. 예: `http://localhost:5000/*`, `http://localhost:8000/*`
3. **비밀정보(Secret) 확인**
   * ‘Credentials’ 탭에서 ‘Client secret’을 확인한다.
   * Flask나 Django 설정 파일에 클라이언트 ID, 클라이언트 비밀정보, Keycloak 서버 주소, Realm 이름 등을 환경 변수로 저장하거나 설정 파일을 통해 안전하게 보관한다.
4. **OIDC 설정값 확인**
   * Realm > ‘OpenID Endpoints’ 혹은 ‘Well-known configuration’을 확인하여 `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `end_session_endpoint` 등을 확인한다.
   * Flask/Django에서 라이브러리를 통해 자동으로 참조할 수 있으나, 설정 파일 혹은 환경 변수에 직접 기술할 수도 있다.

### Flask 연동

#### 라이브러리 선택

Flask 애플리케이션에 Keycloak 연동을 위해서는 다음과 같은 라이브러리를 활용할 수 있다.

* **python-keycloak**: Keycloak Admin REST API와 함께 OpenID Connect 클라이언트 기능을 제공한다.
* **Authlib**: 범용 OAuth/OIDC 라이브러리로 Keycloak OIDC 인증 흐름에 적용 가능하다.
* **keycloak-flask**: Flask에 특화된 Keycloak 인증 처리를 지원한다(서드파티 라이브러리).

여기서는 `python-keycloak` 예시를 간단히 살펴본다.

#### 예시 코드 구조

1. **설치**

   ```bash
   pip install flask python-keycloak
   ```
2. **프로젝트 구조 예시**

   ```
   my_flask_app/
   ├─ app.py
   ├─ requirements.txt
   └─ config.py
   ```
3. **config.py 예시**

   ```python
   KEYCLOAK_SERVER_URL = "http://localhost:8080/auth/"
   KEYCLOAK_REALM = "myrealm"
   KEYCLOAK_CLIENT_ID = "python-flask-client"
   KEYCLOAK_CLIENT_SECRET = "비밀정보"
   KEYCLOAK_REDIRECT_URI = "http://localhost:5000/callback"
   ```
4. **app.py 예시**

   ```python
   from flask import Flask, request, redirect, url_for, session
   from python_keycloak.keycloak_openid import KeycloakOpenID
   from config import (
       KEYCLOAK_SERVER_URL,
       KEYCLOAK_REALM,
       KEYCLOAK_CLIENT_ID,
       KEYCLOAK_CLIENT_SECRET,
       KEYCLOAK_REDIRECT_URI,
   )

   app = Flask(__name__)
   app.secret_key = "some_random_secret_key"

   keycloak_openid = KeycloakOpenID(
       server_url=KEYCLOAK_SERVER_URL,
       client_id=KEYCLOAK_CLIENT_ID,
       realm_name=KEYCLOAK_REALM,
       client_secret_key=KEYCLOAK_CLIENT_SECRET
   )

   @app.route("/")
   def index():
       if "access_token" in session:
           return "로그인 완료. 사용자 정보: " + str(session["user_info"])
       else:
           return "로그인이 필요하다. /login 으로 이동하라."

   @app.route("/login")
   def login():
       auth_url = keycloak_openid.auth_url(
           redirect_uri=KEYCLOAK_REDIRECT_URI,
           scope=["openid", "profile", "email"]
       )
       return redirect(auth_url)

   @app.route("/callback")
   def callback():
       code = request.args.get("code")
       token = keycloak_openid.token(
           grant_type=["authorization_code"],
           code=code,
           redirect_uri=KEYCLOAK_REDIRECT_URI
       )
       session["access_token"] = token["access_token"]
       session["refresh_token"] = token["refresh_token"]
       userinfo = keycloak_openid.userinfo(token["access_token"])
       session["user_info"] = userinfo
       return redirect(url_for("index"))

   if __name__ == "__main__":
       app.run(host="0.0.0.0", port=5000, debug=True)
   ```
5. **동작 원리**
   * `/login`으로 접속하면 Keycloak 로그인 페이지로 리다이렉트된다.
   * 사용자가 로그인하면 Keycloak이 `redirect_uri`로 `authorization_code`를 전달한다.
   * Flask 애플리케이션의 `/callback`에서 코드를 받아 토큰 교환(token exchange)을 수행한다.
   * `access_token`, `refresh_token`, `userinfo` 등을 세션에 저장하여 인증 상태를 유지한다.
   * 보호가 필요한 모든 라우트에서 세션 내 `access_token`을 검사해 인증 여부를 확인한다.

### Django 연동

Django 환경에서도 Keycloak과 OIDC 프로토콜을 통해 연동할 수 있다. Django는 기본적으로 세션과 인증 시스템이 잘 구축되어 있기 때문에, Keycloak에서 받은 토큰을 활용해 사용자 정보를 인증 백엔드 또는 미들웨어에서 처리할 수 있다.

#### 라이브러리 선택

* **django-oidc**
* **Authlib**
* **python-keycloak**

다만, 공식적으로 Django에 최적화된 Keycloak 플러그인은 존재하지 않으므로 범용 OIDC 라이브러리를 활용하는 경우가 많다. 다음은 `Authlib`을 사용하는 간단한 예시를 보여준다.

#### 예시 코드 구조

1. **설치**

   ```bash
   pip install django authlib
   ```
2. **프로젝트 구조 예시**

   ```
   my_django_app/
   ├─ myproject/
   │  ├─ settings.py
   │  ├─ urls.py
   │  ├─ wsgi.py
   │  └─ ...
   └─ manage.py
   ```
3. **settings.py 예시**

   ```python
   import os

   BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
   SECRET_KEY = "some_random_secret_key"
   DEBUG = True

   ALLOWED_HOSTS = ["*"]

   INSTALLED_APPS = [
       "django.contrib.admin",
       "django.contrib.auth",
       "django.contrib.contenttypes",
       "django.contrib.sessions",
       "django.contrib.messages",
       "django.contrib.staticfiles",
       # 필요한 경우 custom app 등록
   ]

   MIDDLEWARE = [
       "django.contrib.sessions.middleware.SessionMiddleware",
       "django.middleware.common.CommonMiddleware",
       "django.middleware.csrf.CsrfViewMiddleware",
       "django.contrib.auth.middleware.AuthenticationMiddleware",
       "django.contrib.messages.middleware.MessageMiddleware",
       "django.middleware.clickjacking.XFrameOptionsMiddleware",
       # 필요 시 OIDC 연동용 미들웨어 추가
   ]

   ROOT_URLCONF = "myproject.urls"

   # Keycloak / OIDC 설정
   KEYCLOAK_SERVER_URL = "http://localhost:8080/auth/"
   KEYCLOAK_REALM = "myrealm"
   KEYCLOAK_CLIENT_ID = "python-django-client"
   KEYCLOAK_CLIENT_SECRET = "비밀정보"
   KEYCLOAK_REDIRECT_URI = "http://localhost:8000/oidc/callback"

   # static, templates 경로 등 일반 Django 설정
   ```
4. **urls.py 예시**

   ```python
   from django.contrib import admin
   from django.urls import path
   from .views import index, login_view, oidc_callback

   urlpatterns = [
       path('admin/', admin.site.urls),
       path('', index, name='index'),
       path('login/', login_view, name='login'),
       path('oidc/callback', oidc_callback, name='oidc_callback'),
   ]
   ```
5. **views.py 예시**

   ```python
   from django.shortcuts import redirect, render
   from django.conf import settings
   from django.urls import reverse
   from authlib.integrations.requests_client import OAuth2Session

   def index(request):
       if request.session.get('access_token'):
           userinfo = request.session.get('userinfo')
           return render(request, 'index.html', {'userinfo': userinfo})
       return render(request, 'index.html', {'userinfo': None})

   def login_view(request):
       client = OAuth2Session(
           client_id=settings.KEYCLOAK_CLIENT_ID,
           client_secret=settings.KEYCLOAK_CLIENT_SECRET
       )
       authorization_endpoint = f"{settings.KEYCLOAK_SERVER_URL}realms/{settings.KEYCLOAK_REALM}/protocol/openid-connect/auth"
       redirect_uri = request.build_absolute_uri(reverse('oidc_callback'))
       scope = ["openid", "profile", "email"]

       uri, state = client.create_authorization_url(
           authorization_endpoint,
           redirect_uri=redirect_uri,
           scope=scope
       )
       request.session['oidc_state'] = state
       return redirect(uri)

   def oidc_callback(request):
       client = OAuth2Session(
           client_id=settings.KEYCLOAK_CLIENT_ID,
           client_secret=settings.KEYCLOAK_CLIENT_SECRET
       )
       token_endpoint = f"{settings.KEYCLOAK_SERVER_URL}realms/{settings.KEYCLOAK_REALM}/protocol/openid-connect/token"
       state = request.session.get('oidc_state')
       redirect_uri = request.build_absolute_uri(reverse('oidc_callback'))
       code = request.GET.get('code')
       if code is None:
           return redirect('/')

       token = client.fetch_token(
           token_endpoint,
           code=code,
           redirect_uri=redirect_uri
       )
       request.session['access_token'] = token['access_token']
       request.session['refresh_token'] = token['refresh_token']

       userinfo_endpoint = f"{settings.KEYCLOAK_SERVER_URL}realms/{settings.KEYCLOAK_REALM}/protocol/openid-connect/userinfo"
       resp = client.get(userinfo_endpoint, token=token)
       request.session['userinfo'] = resp.json()

       return redirect('/')
   ```
6. **동작 원리**
   * 사용자가 `/login/`에 접근하면 Keycloak 로그인 화면으로 이동한다.
   * 정상 로그인 후 코드가 `/oidc/callback`으로 전송된다.
   * Django 뷰에서 받은 코드를 토큰으로 교환하고, `access_token`, `userinfo`를 세션에 저장한다.
   * 이후 `request.session`을 통해 인증된 사용자 정보를 확인 가능하다.

### 주의사항

* **HTTPS 사용** 운영 환경에서는 HTTPS를 반드시 사용해야 한다. Keycloak 인증 흐름에서 토큰이 노출되면 심각한 보안 문제가 발생할 수 있다.
* **클라이언트 구분** Flask 애플리케이션과 Django 애플리케이션을 동시에 사용한다면, 각 애플리케이션별로 Keycloak에 별도의 클라이언트를 생성하는 것이 좋다.
* **토큰 검증** 수신한 `access_token`을 백엔드에서 다시 검증하거나 만료 시간을 철저하게 검사하는 로직이 필요할 수 있다. 표준 OIDC 라이브러리를 사용하면 토큰 검증 로직을 자동으로 처리하지만, 커스텀 구현 시에는 토큰의 서명 유효성, 발행 시간, 만료 시간을 점검해야 한다.
* **세션 관리** Django나 Flask에서 기본 제공되는 세션은 서버 세션(cookie 기반)이며, 토큰이 저장된다. 민감 정보가 세션에서 노출되지 않도록 쿠키 보안 설정(HTTPOnly, Secure)을 적용한다.
* **로깅 및 예외 처리** Keycloak 인증 과정에서 발생하는 예외(네트워크 문제, 토큰 교환 실패 등)에 대한 로깅과 예외 처리를 반드시 구현한다.
* **갱신 토큰(refresh\_token)** 인증 세션을 오래 유지해야 한다면 `refresh_token`을 사용하여 만료된 `access_token`을 재발급할 수 있다. Keycloak에서 `refresh_token` 만료 정책을 설정해 둔다.

위와 같은 방식으로 Python Flask, Django 애플리케이션에서도 Keycloak을 통한 인증·권한 관리를 간편하게 적용할 수 있다. OIDC 프로토콜과 Keycloak에서 제공하는 풍부한 기능을 활용하여, 어플리케이션별 인증 로직을 최소화하면서 일관된 보안 정책을 유지하는 것이 중요하다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://booiljung.gitbook.io/booil-jung/docs/server_archiecture/keycloak/chapter_13/1305.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
