Stay Hungry,Stay Foolish!

docker compose fullstack example -- keycloak web grant-type: password

fastapi-react-postgres-keycloak-sso

https://github.com/fanqingsong/fastapi-react-postgres-keycloak-sso

 

version: "3"

services:
  nginx:
    image: nginx:1.17
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./logs/nginx:/var/log/nginx
    ports:
      - 80:80
    depends_on:
      - frontend
      - backend

  backend:
    restart: unless-stopped
    build:
      context: backend
      dockerfile: Dockerfile
    env_file:
      - .env
    environment:
      PYTHONPATH: .
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_USER}
    volumes:
      - ./logs/backend:/logs
    ports:
      - 8888:8888
    depends_on:
      - postgres
      - keycloak

  frontend:
    build:
      context: frontend
      dockerfile: Dockerfile
    ports:
      - 8000:8000
    environment:
      NODE_ENV: development
      CHOKIDAR_USEPOLLING: "true"

  keycloak:
    image: jboss/keycloak:12.0.4
    environment:
      DB_VENDOR: POSTGRES
      DB_SCHEMA: public
      DB_ADDR: keycloak_postgres
      DB_DATABASE: ${KEYCLOAK_DB_DATABASE}
      DB_USER: ${KEYCLOAK_DB_USER}
      DB_PASSWORD: ${KEYCLOAK_DB_PASSWORD}
      KEYCLOAK_USER: ${KEYCLOAK_ADMIN_USERNAME}
      KEYCLOAK_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
      JDBC_PARAMS: "useSSL=false"
    ports:
      - 8080:8080
    depends_on:
      - keycloak_postgres

  postgres:
    image: postgres
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    ports:
      - 5432:5432
    volumes:
      - postgres_data:/var/lib/postgresql/data

  keycloak_postgres:
    image: postgres
    volumes:
      - keycloak_postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${KEYCLOAK_DB_DATABASE}
      POSTGRES_USER: ${KEYCLOAK_DB_USER}
      POSTGRES_PASSWORD: ${KEYCLOAK_DB_PASSWORD}

volumes:
  postgres_data:
  keycloak_postgres_data:

 

frontend docker wrapper

https://typeofnan.dev/how-to-serve-a-react-app-with-nginx-in-docker/

FROM node:lts as build

WORKDIR /app

COPY ./app/ /app
RUN npm install --legacy-peer-deps --registry=https://registry.npm.taobao.org
RUN npm run build

FROM nginx

COPY ./nginx.conf /etc/nginx/conf.d/angular.conf
COPY --from=build /app/build /usr/share/nginx/html
RUN chmod -R o+r /usr/share/nginx/html/

 

env set

https://docs.docker.com/compose/environment-variables/set-environment-variables/

.env文件向docker compose文件注入环境变量

 

docker compose文件中的

env-file

environment

属性,可以向容器内注入环境变量。

 

python-keycloak

https://python-keycloak.readthedocs.io/en/latest/

from keycloak import KeycloakOpenID

# Configure client
keycloak_openid = KeycloakOpenID(server_url="http://localhost:8080/auth/",
                                 client_id="example_client",
                                 realm_name="example_realm",
                                 client_secret_key="secret")

# Get WellKnown
config_well_known = keycloak_openid.well_known()

# Get Code With Oauth Authorization Request
auth_url = keycloak_openid.auth_url(
    redirect_uri="your_call_back_url",
    scope="email",
    state="your_state_info")

# Get Access Token With Code
access_token = keycloak_openid.token(
    grant_type='authorization_code',
    code='the_code_you_get_from_auth_url_callback',
    redirect_uri="your_call_back_url")


# Get Token
token = keycloak_openid.token("user", "password")
token = keycloak_openid.token("user", "password", totp="012345")

# Get token using Token Exchange
token = keycloak_openid.exchange_token(token['access_token'], "my_client", "other_client", "some_user")

# Get Userinfo
userinfo = keycloak_openid.userinfo(token['access_token'])

# Refresh token
token = keycloak_openid.refresh_token(token['refresh_token'])

# Logout
keycloak_openid.logout(token['refresh_token'])

 

usage

"""Module used for keycloak backend calls."""
import os
import typing as tp

from fastapi import Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer
from jose.exceptions import JWTError
from keycloak.exceptions import KeycloakAuthenticationError, KeycloakGetError
from keycloak.keycloak_openid import KeycloakOpenID

REALM = 'master'
KEYCLOAK_BASEURL = f'http://localhost:8080/auth/realms' \
                   f'/{REALM}/protocol/openid-connect'

oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{KEYCLOAK_BASEURL}/token")

keycloak_openid = KeycloakOpenID(
    server_url=os.environ.get("KEYCLOAK_SERVER_URL"),
    realm_name=os.environ.get("KEYCLOAK_REALM_NAME"),
    client_id=os.environ.get("KEYCLOAK_CLIENT_ID"),
    client_secret_key=os.environ.get("KEYCLOAK_CLIENT_SECRET_KEY"),
)

KEYCLOAK_PUBLIC_KEY = (
    "-----BEGIN PUBLIC KEY-----\n"
    f"{keycloak_openid.public_key()}"
    "\n-----END PUBLIC KEY-----"
)


async def authenticate_user(username: str, password: str) -> tp.Dict[str, str]:
    """Authenticate user with Keycloak backend.

    Args:
        username
        password

    Returns:
        Access token and refresh token with their expiration time
    """
    try:
        return keycloak_openid.token(username, password)
    except KeycloakAuthenticationError as error:
        raise HTTPException(status_code=401, detail="Invalid credentials") from error


async def verify_token(token: str = Depends(oauth2_scheme)) -> tp.Dict[str, str]:
    """Verify token with Keycloak public key.

    Args:
        token: access token to decode

    Returns:
        Token decoded
    """
    try:
        return keycloak_openid.decode_token(
            token,
            key=KEYCLOAK_PUBLIC_KEY,
            options={"verify_signature": True, "verify_aud": False, "exp": True},
        )
    except (KeycloakGetError, JWTError) as error:
        raise HTTPException(
            status_code=401, detail=str(error), headers={"WWW-Authenticate": "Bearer"}
        ) from error


async def refresh_token(token: str) -> tp.Dict[str, str]:
    try:
        return keycloak_openid.refresh_token(token)
    except (KeycloakGetError) as error:
        raise HTTPException(status_code=401, detail=str(error)) from error


async def logout(token: str) -> tp.Dict[str, str]:
    try:
        return keycloak_openid.logout(token)
    except (KeycloakGetError) as error:
        raise HTTPException(status_code=401, detail=str(error)) from error

 

posted @ 2023-05-14 20:55  lightsong  阅读(132)  评论(0编辑  收藏  举报
Life Is Short, We Need Ship To Travel