LoginSignup
6
6

More than 1 year has passed since last update.

Docker + Keycloak + FastAPIを用いたローカル OAuth2.0 Authorization Code Grant 検証環境構築 メモ

Last updated at Posted at 2021-07-31
  • 学習目的でKeycloakを使ったAuthorization Code Grantを検証するための環境を構築したので、メモとして残しておく。
  • アクセストークンを取得するところまで。

構成

  • Dockerを用いた以下の構成で検証環境を構築する。

fastapi_keycloak_mysql.png

keycloak    ___ docker-compose.yml
            |__ app.env
            |__ app     ___ Dockerfile
                        |__ requirements.txt
                        |__ api _ main.py

docker-compose.yml

  • FastAPI - Keycloak - MySQLコンテナ定義
version: "3"

volumes:
  mysql_data:
    driver: local

services:
  mysql:
    image: mysql:5.7
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: keycloak
      MYSQL_USER: keycloak
      MYSQL_PASSWORD: password
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    container_name: "keycloak"
    environment:
      DB_VENDOR: MYSQL
      DB_ADDR: mysql
      DB_DATABASE: keycloak
      DB_USER: keycloak
      DB_PASSWORD: password
      KEYCLOAK_USER: admin
      KEYCLOAK_PASSWORD: P@ssw0rd
    ports:
      - 8080:8080
    depends_on:
      - mysql
  app:
    container_name: "app"
    env_file: app.env
    build: ./app
    volumes:
      - ./app/api:/usr/src/server
    ports:
      - "8000:8000"

app.env

  • 環境変数設定
    • 一度Keycloakコンテナを起動して、登録したクライアント情報を設定する。
APP_BASE_URL=http://localhost:8000/
KEYCLOAK_BASE_URL_LOCALHOST=http://localhost:8080/
KEYCLOAK_BASE_URL_CONTAINER_NAME=http://keycloak:8080/
CLIENT_ID=YOUR_CLIENT_ID
CLIENT_SECRET=YOUR_CLIENT_SECRET
REDIRECT_URI=http://localhost:8000/auth/callback

requirements.txt

  • Python依存ライブラリ
fastapi
requests
uvicorn

Dockerfile

  • 依存ライブラリインストールとFastAPI起動オプションを指定。
FROM python:3.7

WORKDIR /usr/src/server
ADD requirements.txt .
RUN pip install -r requirements.txt

# uvicornのオプションに--reloadを付与し、
# main.pyの編集と同時に変更内容を反映させる。
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]

main.py

  • 以下を行う。
    • Keycloak Authorization Endpointへのリダイレクト(login)
    • Keycloak Authorization Responseの受信+トークンリクエスト(auth)
import ast
import urllib.parse as parse
import os
import requests
import uvicorn
from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import RedirectResponse
import hashlib

# 環境変数取得
# FastAPI アプリ用
APP_BASE_URL = os.getenv("APP_BASE_URL")
APP_CLIENT_ID = os.getenv("CLIENT_ID")
APP_CLIENT_SECRET = os.getenv('CLIENT_SECRET')
APP_REDIRECT_URI = os.getenv('REDIRECT_URI')

# Keycloak用
# Authorization Endpoint
KEYCLOAK_BASE_URL_LOCALHOST = os.getenv("KEYCLOAK_BASE_URL_LOCALHOST")
# "Master"は自身の環境のレルム名を指定する。
AUTH_BASE_URL = (
    f"{KEYCLOAK_BASE_URL_LOCALHOST}auth/realms/Master"
    "/protocol/openid-connect/auth"
)
# Token Endpoint
# コンテナ間通信するため、localhostではなくコンテナ名を指定
# "Master"は自身の環境のレルム名を指定する。
KEYCLOAK_BASE_URL_CONTAINER_NAME = os.getenv(
    "KEYCLOAK_BASE_URL_CONTAINER_NAME")
TOKEN_URL = (
    f"{KEYCLOAK_BASE_URL_CONTAINER_NAME}auth/realms/master"
    "/protocol/openid-connect/token"
)

app = FastAPI()

# Keycloak Authorization Endpointへのリダイレクト
@app.get("/auth/login")
async def login() -> RedirectResponse:
    # ステート生成
    state = hashlib.sha256(os.urandom(32)).hexdigest()
    # Authorization Endpointへリダイレクト
    AUTH_URL = AUTH_BASE_URL + '?{}'.format(parse.urlencode({
        'client_id': APP_CLIENT_ID,
        'redirect_uri': APP_REDIRECT_URI,
        'state': state,
        'response_type': 'code'
    }))
    response = RedirectResponse(AUTH_URL)
    # ステート保存
    response.set_cookie(key="AUTH_STATE", value=state)
    return response


# Token Request
def get_token(code):

    params = {
        'client_id': APP_CLIENT_ID,
        'client_secret': APP_CLIENT_SECRET,
        'grant_type': 'authorization_code',
        'redirect_uri': APP_REDIRECT_URI,
        'code': code
    }
    x = requests.post(TOKEN_URL, params, verify=False).content.decode('utf-8')
    return ast.literal_eval(x)


# Redirection Endpoint
# ステートと認可コードを受け取る。
# ステート検証後、トークンリクエストを実行する。
@app.get("/auth/callback")
async def auth(request: Request, code: str, state: str) -> RedirectResponse:
    # State検証
    if state != request.cookies.get("AUTH_STATE"):
        return {"error": "state_verification_failed"}
    return get_token(code)

if __name__ == "__main__":
    uvicorn.run(app, port=8000, loop="asyncio")

動作確認

  • コンテナを起動する
  docker-compose up
  • Keycloak Admin コンソールにアクセスする。
  http://localhost:8080

※ログイン情報はdocker-composeに記載

  • クライアント(Clients)を登録する。
    ※各種登録情報は、Keycloak: Authorization Code Grant Exampleを参考にする。

  • テストユーザー(Users)を登録する。

  • コンテナを再起動する。

  docker-compose down
  docker-compose build
  docker-compose up
  • Keycloak Authorization Endpointリダイレクト用エンドポイントにアクセスする。
  http://localhost:8000/auth/login
  • ユーザー認証を行う。

  • FastAPI側のリダイレクトURIにリダイレクトされ、ブラウザに以下のようなトークンレスポンスJSONが表示される。

  {
      "access_token": "...",
      "expires_in": 60,
      "refresh_expires_in": 1800,
      "refresh_token": "...",
      "token_type": "Bearer",
      "not-before-policy": 0,
      "session_state": "...",
      "scope": "profile email"
  }

参考情報

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6